垂直打印树

时间:2013-12-11 13:59:04

标签: algorithm data-structures binary-tree

要了解相同的垂直线,我们需要先定义水平距离。如果两个节点具有相同的水平距离(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方法或类似方法以更好的方式做到这一点。可以帮助别人吗?

9 个答案:

答案 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_leftzip_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_leftzip_rightzip_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,只需使用数组。

  1. 每个节点都有一个虚拟pos,左边的孩子有pos-1,右边的孩子有pos + 1

  2. 遍历整棵树(无论顺序如何),记录最左边的pos * min_pos *和最右边的pos * max_pos *。 (第一次通过

  3. 创建一个数组长度为max_pos-min_pos + 1,每个插槽都是一个空列表

  4. 再次遍历整个树,使用数组,min_pos并继续跟踪pos。访问节点时,我们得到它的pos,我们通过索引pos - min_pos将节点添加到数组中。 (第二次传递

  5. 打印数组中的所有列表(第3遍


  6. 以下是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}));
}