要了解相同的垂直线,我们需要先定义水平距离。如果两个节点具有相同的水平距离(HD),则它们位于同一垂直线上。 HD的想法很简单。根的HD为0,右边缘(连接到右子树的边缘)被认为是+1水平距离而左边缘被认为是-1水平距离。例如,在下面的树中,节点4的HD为-2,节点2的HD为-1,5和6的HD为0,节点7的HD为+2。
示例:
1
/ \
2 3
/ \ / \
4 5 6 7
树有5条垂直线
Vertical-Line-1只有一个节点4
Vertical-Line-2:只有一个节点2
Vertical-Line-3:有三个节点:1,5,6
Vertical-Line-4:只有一个节点3
Vertical-Line-5:只有一个节点7
现在为树
1
/ \
2 3
/ \ / \
4 5 6 7
/ \ / \
8 9 10 11
对于上面的树,我们应该从上到下从左到右得到每个垂直水平的输出
8
4
2 9
1 5 6或1 6 5(因为6和5处于相同的垂直水平,同样的HD,顺序无关紧要)
3 10
7
11
这样做的一种方法是简单地创建HD的多图,并进行水平顺序遍历,并将值推送到相应的HD索引。按顺序进行此操作将保证我们从上到下垂直访问。然后将节点从最低HD打印到最高HD,从而实现从左到右的约束。
我已经阅读过某个地方,我们可以使用Doubly-Link List方法或类似方法以更好的方式做到这一点。可以帮助别人吗?
答案 0 :(得分:4)
你将不得不访问每个节点一次以获得它的价值 - 所以除了完全遍历之外别无选择,正如你所说的那样。在遍历时跟踪当前节点的水平距离是微不足道的。
在遍历整个树之前,您无法知道要打印的第一个值,因此您必须在打印任何内容之前将所有值收集到数据结构中。所以唯一的选择是使用什么数据结构。
您可以使用哪些结构取决于您的语言。
我会使用您的语言相当于Java Map<HorizontalDistance,List<Node>>
。
我认为Map
没有任何特殊要求。 List
需要便宜才能添加。如果它是一个链表,它应该至少保持一个指向它尾部的指针。不能有许多不符合此要求的主流列表实现。标准Java LinkedList
可以。
所以,你按顺序遍历树,为每个节点调用它:
private void addNode(Map<HorizontalDistance,List<Node>> data,
HorizontalDistance hd,
Node node)
{
List<Node> list = data.get(hd); // this is cheap
list.add(node); // this is cheap too
}
...然后通过迭代地图键打印出来,打印每个列表。
我想,你可以用一个数组/列表替换Map,将你的正HD映射到偶数索引,将你的负数映射到奇数。
int index = hd < 0 ? ( -2 * hd - 1 ) : 2 * hd;
取决于地图实现 - 以及数组实现 - 以及在某些语言中,无论您是否已经知道足够大小的数据 - 这可能更快,更节省内存。
答案 1 :(得分:0)
我怀疑双链表解决方案比Map解决方案快得多,但我认为你可以这样做:
每次向左移动树时,都会传递双链表的左节点+该节点的水平距离(即crt hd-1)。类似于右翼的情况。
答案 2 :(得分:0)
此任务可以通过称为zipper的简单结构来实现。这种结构非常适合在列表中保持状态。技术上的结构是将(单个)链表分成两部分。首先是以相反顺序存储的当前位置左侧(或前面)列表的一部分,第二部分是左侧成员。想象它就像这样的结构
struct {
list *revleft;
list *right;
};
或者在某些功能语言中将列表作为单个链表,例如Erlang
{Left, Right}
这些列表的每个成员都是包含一条垂直线的列表。
然后我们必须定义操作zip_left
和zip_right
。每个操作都将一个元素从拉链的一侧移动到另一侧。如果没有要删除的元素,此操作也必须使空列表。例如在Erlang中:
zip_left({[], R}) -> {[], [[]|R]}; % make empty list and add to right
zip_left({[H|T], R}) -> {T, [H |R]}. % move head to right
zip_right({L, []}) -> {[[]|L], []}; % make empty list and add to left
zip_right({L, [H|T]}) -> {[H |L], T}. % move head to left
[H|T]
是删除列表头部或添加新头部列表的操作,具体取决于->
的哪一侧。它在左侧移除,在右侧添加。
然后我们需要定义操作zip_add
,它将树节点值添加到相应的垂直线。只是为了惯例,假设当前值是拉链右侧的头部。我们必须处理空列表。
zip_add(V, {L, [ ]}) -> {L, [[V] ]}); % add value to empty list
zip_add(V, {L, [H|R]}) -> {L, [[V|H]|R]}. % add value to right head
现在我们必须简单地在树周围发送拉链。当我们向右移动时,我们将zip拉到右边,然后返回从子树返回到左边。当我们向左移动时,我们将拉链向左移动,然后向右移动。哪个顺序无关紧要。它仅影响每条垂直线中的值的顺序。
-record(node, {
value,
left = nil,
right = nil
}).
zip_new() -> {[], []}. % new empty zipper
get_verical_lines(Root) ->
{L, R} = get_verical_lines(Root, zip_new()),
tl(lists:reverse(L, R)). % concatenate zipper and cut off surplus empty list
get_verical_lines(nil, Zip) -> Zip;
get_verical_lines(#node{value = V, left = L, right = R}, Zip) ->
Zip1 = zip_right(get_verical_lines(L, zip_left(Zip))),
Zip2 = zip_left(get_verical_lines(R, zip_right(Zip1))),
zip_add(V, Zip2).
就是这样。所有操作zip_left
,zip_right
和zip_add
都是O(1)
次操作。在每个节点中,我们执行它们固定的次数(zip_left
x2,zip_right
x2,zip_add
x1)所以它很好O(N)
只有简单的单链表结构。任何语言的实现都非常简单,除非它不那么冗长。
第一个树形式问题
> io:write(vertical_tree:get_verical_lines({node, 1, {node, 2, {node, 4, nil, nil}, {node, 5, nil, nil}}, {node, 3, {node, 6, nil, nil}, {node, 7, nil, nil}}})).
[[4],[2],[1,6,5],[3],[7]]ok
第二棵树
> io:write(vertical_tree:get_verical_lines({node, 1, {node, 2, {node, 4, {node, 8, nil, nil}, {node, 9, nil, nil}}, {node, 5, nil, nil}}, {node, 3, {node, 6, nil ,nil},{node, 7, {node, 10, nil, nil}, {node, 11, nil, nil}}}})).
[[8],[4],[2,9],[1,6,5],[3,10],[7],[11]]ok
有完整的模块code,包括树漂亮的打印机。
> io:put_chars(vertical_tree:draw({node, 1, {node, 2, {node, 4, nil, nil}, {node, 5, nil, nil}}, {node, 3, {node, 6, nil, nil}, {node, 7, nil, nil}}})).
1
/ \
2 3
/ \ / \
4 5 6 7
ok
> io:put_chars(vertical_tree:draw({node, 1, {node, 2, {node, 4, {node, 8, nil, nil}, {node, 9, nil, nil}}, {node, 5, nil, nil}}, {node, 3, {node, 6, nil ,nil},{node, 7, {node, 10, nil, nil}, {node, 11, nil, nil}}}})).
1
/ \
2 3
/ \ / \
4 5 6 7
/ \ / \
8 9 10 11
ok
BTW拉链的类型比单链表更多。例如,可以制作用于遍历树的拉链,它允许遍历列表而不进行递归或仅使用尾递归函数。
答案 3 :(得分:0)
如果您想要真正打印它们,而不是仅存储它们,则需要 3次完整遍历。
如果我们使用map
而不是数组,而且我们只需要通过垂直线存储列表中的节点,而不是打印,那么 1遍完全遍历就足够了。
下面的想法假设我们不能使用map
,只需使用数组。
每个节点都有一个虚拟pos,左边的孩子有pos-1,右边的孩子有pos + 1
遍历整棵树(无论顺序如何),记录最左边的pos * min_pos *和最右边的pos * max_pos *。 (第一次通过)
创建一个数组长度为max_pos-min_pos + 1,每个插槽都是一个空列表
再次遍历整个树,使用数组,min_pos并继续跟踪pos。访问节点时,我们得到它的pos,我们通过索引pos - min_pos
将节点添加到数组中。 (第二次传递)
打印数组中的所有列表(第3遍)
以下是OCaml中的代码,没有最后一个打印部分。
type 'a btree = Empty | Node of 'a btree * 'a * 'a btree
let min3 x y z = min (min x y) z
let max3 x y z = max (max x y) z
let range btree =
let rec get_range (min_pos, max_pos) pos = function
| Empty -> min_pos, max_pos
| Node (l, _, r) ->
let min1, max1 = get_range (min_pos,max_pos) (pos-1) l in
let min2, max2 = get_range (min_pos,max_pos) (pos+1) r in
min3 min1 min2 pos, max3 max1 max2 pos
in
get_range (0, 0) 0 btree
let by_vertical (min_pos, max_pos) btree =
let a = Array.make (max_pos-min_pos+1) [] in
let rec traversal pos = function
| Empty -> ()
| Node (l, k, r) -> (
a.(pos-min_pos) <- k::a.(pos-min_pos);
traversal (pos-1) l;
traversal (pos+1) r;
)
in
traversal 0 btree;
a;;
let in_vertical btree = by_vertical (range btree) btree
答案 4 :(得分:0)
检查从根到所有节点的水平距离,如果两个节点具有相同的水平距离,则它们在同一垂直线上。将此水平距离存储到hash map
。
C ++代码
struct Node
{
int val;
Node* left, *right;
};
void getHD(Node* root, int hd, map<int, vector<int>> &hmap)
{
if (root == nullptr)
return;
// store current node in hash map
hmap[hd].push_back(root->val);
getHD(root->left, hd-1, hmap);
getHD(root->right, hd+1, hmap);
}
void printHD(Node* root)
{
map<int, vector<int>> hmap;
getHD(root, 0, hmap);
for (auto it = std::begin(hmap), en = std::end(hmap); it != en; ++it)
{
for (auto v : it->second)
cout << v << " ";
cout << endl;
}
}
答案 5 :(得分:0)
双重链接列表方法: 将每个垂直列表视为双向链表中的节点: 节点1&lt; - &gt;节点2&lt; - &gt; Node3&lt; - &gt; Node4 .....
节点1:8 节点2:4 节点3:2,9 节点4:1,5,6 ...
这个想法是:当你走到左边的孩子/父母时,你去链表中的左边节点,将树节点存储到当前的链表节点; 当你走到右边的孩子/父母那里时,你会在链接列表中找到正确的节点,然后存储到上面的节点中。
最左边的节点会存储最左边的垂直列表,在这种情况下是Node1,你可以从左到右打印这个链表来获得最终结果。
答案 6 :(得分:0)
public List<ArrayList<TreeNode>> getVerticalView() {
List<ArrayList<TreeNode>> ans = new ArrayList<ArrayList<TreeNode>>();
Map<Integer, List<TreeNode>> map = new LinkedHashMap<Integer, List<TreeNode>>(); // Linked
// hash
// map,
// entry
// set
// will
// run
// in
// insertion
// order
getVerticalpart(root, map, 0);
int index = 0;
for (Map.Entry<Integer, List<TreeNode>> entry : map.entrySet()) {
ans.add(index, (ArrayList<TreeNode>) entry.getValue());
index++;
}
return ans;
}
private void getVerticalpart(TreeNode root, Map<Integer, List<TreeNode>> map, int i) {
if (root == null)
return;
/**
* Idea is to to inorder traversal of tree. Just put the node in the map
* according to value of i
*/
getVerticalpart(root.left, map, i - 1);
if (!map.containsKey(i)) {
map.put(i, new ArrayList<>());
}
List<TreeNode> l = map.get(i);
l.add(root);
getVerticalpart(root.right, map, i + 1);
}
答案 7 :(得分:0)
这是java实现(基于非hashmap)
public static <T extends Comparable<? super T>> List<List<T>> verticalView(BinaryTreeNode<T> node) {
List<List<T>> result = new ArrayList<List<T>>();
MutableInteger min = new MutableInteger(0);
MutableInteger max = new MutableInteger(0);
findMinMaxHD(node, min, max, 0);
printVeritcalVew(node, min.getValue(), max.getValue(), result);
return result;
}
private static <T extends Comparable<? super T>> void findMinMaxHD(BinaryTreeNode<T> node, MutableInteger min, MutableInteger max, int hd) {
if (node == null) {
return;
}
min.updateForMin(hd);
max.updateForMax(hd);
findMinMaxHD(node.getLeft(), min, max, hd - 1);
findMinMaxHD(node.getRight(), min, max, hd + 1);
}
private static <T extends Comparable<? super T>> void printVeritcalVew(BinaryTreeNode<T> node, Integer min, Integer max, List<List<T>> result) {
if (node == null) {
return ;
}
for (int lineNo = min; lineNo <= max; lineNo++) {
List<T> lineResult = new ArrayList<T>();
doPrintVerticalView(node, lineNo, 0, lineResult);
result.add(lineResult);
}
}
private static <T extends Comparable<? super T>> void doPrintVerticalView(BinaryTreeNode<T> node, int lineNo, int hd, List<T> lineResult) {
if (node == null) {
return ;
}
if (lineNo == hd) {
lineResult.add(node.getData());
}
doPrintVerticalView(node.getLeft(), lineNo, hd - 1, lineResult);
doPrintVerticalView(node.getRight(), lineNo, hd + 1, lineResult);
}
这是单元测试用例
@Test
public void printVerticalViewTest() {
BinaryTreeNode<Integer> node = BinaryTreeUtil.<Integer>fromInAndPostOrder(new Integer[]{4,2,5,1,6,3,7}, new Integer[]{4,5,2,6,7,3,1});
List<List<Integer>> rightView = BinaryTreeUtil.<Integer>verticalView(node);
assertThat(rightView.size(), is(5));
assertThat(rightView.get(0).toArray(new Integer[0]), equalTo(new Integer[]{4}));
assertThat(rightView.get(1).toArray(new Integer[0]), equalTo(new Integer[]{2}));
assertThat(rightView.get(2).toArray(new Integer[0]), equalTo(new Integer[]{1,5,6}));
assertThat(rightView.get(3).toArray(new Integer[0]), equalTo(new Integer[]{3}));
assertThat(rightView.get(4).toArray(new Integer[0]), equalTo(new Integer[]{7}));
}
这是可变整数类
public class MutableInteger {
private Integer value;
public MutableInteger(Integer val) {
this.value = val;
}
public boolean updateForMax(Integer newValue) {
if (this.value < newValue) {
this.value = newValue;
return true;
}
return false;
}
public boolean updateForMin(Integer newValue) {
if (this.value > newValue) {
this.value = newValue;
return true;
}
return false;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
@Override
public String toString() {
return String.valueOf(value);
}
答案 8 :(得分:0)
这是基于Map的java实现
public static<T extends Comparable<? super T>> List<List<T>> mapBasedVerticalView(BinaryTreeNode<T> node) {
Map<Integer, List<BinaryTreeNode<T>>> map = new TreeMap<Integer, List<BinaryTreeNode<T>>>();
List<List<T>> result = new ArrayList<List<T>>();
populateHDMap(node, 0 , map);
populateResult(map, result);
return result;
}
private static<T extends Comparable<? super T>> void populateHDMap(BinaryTreeNode<T> node, int hd, Map<Integer, List<BinaryTreeNode<T>>> map) {
if (node == null) {
return ;
}
updateHDNode(node, hd, map);
populateHDMap(node.getLeft(), hd - 1, map);
populateHDMap(node.getRight(), hd + 1, map);
}
private static <T extends Comparable<? super T>> void updateHDNode(BinaryTreeNode<T> node, Integer hd, Map<Integer, List<BinaryTreeNode<T>>> map) {
List<BinaryTreeNode<T>> list = map.get(hd);
if (list == null) {
list = new ArrayList<BinaryTreeNode<T>>();
map.put(hd, list);
}
list.add(node);
}
private static<T extends Comparable<? super T>> void populateResult(Map<Integer, List<BinaryTreeNode<T>>> map, List<List<T>> result) {
for (Map.Entry<Integer, List<BinaryTreeNode<T>>> entry : map.entrySet()) {
List<T> items = new ArrayList<T>();
for (BinaryTreeNode<T> bt :entry.getValue()) {
items.add(bt.getData());
}
result.add(items);
}
}
这是单元测试
@Test
public void printMapBasedVerticalViewTest() {
BinaryTreeNode<Integer> node = BinaryTreeUtil.<Integer>fromInAndPostOrder(new Integer[]{4,2,5,1,6,3,7}, new Integer[]{4,5,2,6,7,3,1});
List<List<Integer>> rightView = BinaryTreeUtil.<Integer>mapBasedVerticalView(node);
assertThat(rightView.size(), is(5));
assertThat(rightView.get(0).toArray(new Integer[0]), equalTo(new Integer[]{4}));
assertThat(rightView.get(1).toArray(new Integer[0]), equalTo(new Integer[]{2}));
assertThat(rightView.get(2).toArray(new Integer[0]), equalTo(new Integer[]{1,5,6}));
assertThat(rightView.get(3).toArray(new Integer[0]), equalTo(new Integer[]{3}));
assertThat(rightView.get(4).toArray(new Integer[0]), equalTo(new Integer[]{7}));
}