简体   繁体   English

打印一边的二叉树

[英]print a binary tree on its side

How can you print a binary tree on its side so the output looks like this? 如何在其侧面打印二叉树,以便输出如下所示?

   __/a
__/  \b
  \   _/c
   \_/ \d
     \e

(Prettier ascii-art welcome) (更漂亮的ascii-art欢迎)

Here's some code that doesn't quite work: 这里有一些代码不太合适:

def print_tree(tree):
    def emit(node,prefix):
        if "sequence" in node:
            print "%s%s"%(prefix[:-1],node["name"])
        else:
            emit(node["left"],"%s_/ "%prefix.replace("/ "," /")[:-1].replace("_"," "))
            emit(node["right"],"%s \\ "%prefix.replace("\\ "," \\")[:-1])
    emit(tree,"")    

Which outputs this: 哪个输出:

      _/hg19
    _/ \rheMac2
  _/ \mm9
  /\_/bosTau4
  /  \_/canFam2
_/     \pteVam1
 \_/loxAfr3
   \dasNov2

Scope creep : it would be excellent if you could pass in a function that will return the string to print of any node; 范围蔓延 :如果您可以传入一个函数来返回字符串以打印任何节点,那将是非常好的; in this way, I can sometimes print information about non-leave nodes too. 通过这种方式,我有时也可以打印有关非离开节点的信息。 So whether a node has anything to print is controlled by the function passed in as a parameter. 因此,节点是否有任何要打印的内容由作为参数传入的函数控制。

Here's some test-data in JSON: 这是JSON中的一些测试数据:

{
    "left": {
        "left": {
            "left": {
                "left": {
                    "name": "hg19", 
                    "sequence": 0
                }, 
                "right": {
                    "name": "rheMac2", 
                    "sequence": 1
                }
            }, 
            "right": {
                "name": "mm9", 
                "sequence": 2
            }
        }, 
        "right": {
            "left": {
                "name": "bosTau4", 
                "sequence": 3
            }, 
            "right": {
                "left": {
                    "name": "canFam2", 
                    "sequence": 4
                }, 
                "right": {
                    "name": "pteVam1", 
                    "sequence": 5
                }
            }
        }
    }, 
    "right": {
        "left": {
            "name": "loxAfr3", 
            "sequence": 6
        }, 
        "right": {
            "name": "dasNov2", 
            "sequence": 7
        }
    }
}

Here's some code that implements the general, recursive approach described elsewhere. 这里有一些代码实现了其他地方描述的通用递归方法。 The internal representation of a tree is either a string (leaf) or a tuple (pair) of sub-nodes. 树的内部表示是子节点的字符串(叶子)或元组(对)。 The internal representation of the intermediate "fragment" of a node is the tuple (above, below, lines) , where above and below are number of lines above and below the root, and lines is an iterator over each partial line (without spaces to the left). 节点的中间“片段”的内部表示是元组(above, below, lines) ,其中abovebelow是根上下的行数,而lines是每个部分行上的迭代器(没有空格到左边)。

#!/usr/local/bin/python3.3

from itertools import chain
from random import randint


def leaf(t):
    return isinstance(t, str)

def random(n):
    def extend(t):
        if leaf(t):
            return (t+'l', t+'r')
        else:
            l, r = t
            if randint(0, 1): return (l, extend(r))
            else: return (extend(l), r)
    t = ''
    for _ in range(n-1): t = extend(t)
    return t

def format(t):
    def pad(prefix, spaces, previous):
        return prefix + (' ' * spaces) + previous
    def merge(l, r):
        l_above, l_below, l_lines = l
        r_above, r_below, r_lines = r
        gap = r_below + l_above
        gap_above = l_above
        gap_below = gap - gap_above
        def lines():
            for (i, line) in enumerate(chain(r_lines, l_lines)):
                if i < r_above:
                    yield ' ' + line
                elif i - r_above < gap_above:
                    dash = '_' if i - r_above == gap_above - 1 else ' '
                    if i < r_above + r_below:
                        yield pad(dash + '/', 2 * (i - r_above), line)
                    else:
                        yield pad(dash + '/', 2 * gap_below - 1, line)
                elif i - r_above - gap_above < gap_below:
                    if i < r_above + r_below:
                        yield pad(' \\', 2 * gap_above - 1, line)
                    else:
                        spaces = 2 * (r_above + gap_above + gap_below - i - 1)
                        yield pad(' \\', spaces, line)
                else:
                    yield ' ' + line
        return (r_above + gap_above, gap_below + l_below, lines())
    def descend(left, t):
        if leaf(t):
            if left:
                return (1, 0, [t])
            else:
                return (0, 1, [t])
        else:
            l, r = t
            return merge(descend(True, l), descend(False, r))
    def flatten(t):
        above, below, lines = t
        for (i, line) in enumerate(lines):
            if i < above: yield (' ' * (above - i - 1)) + line
            else: yield (' ' * (i - above)) + line
    return '\n'.join(flatten(descend(True, t)))


if __name__ == '__main__':
    for n in range(1,20,3):
        tree = random(n)
        print(format(tree))

Here's some example output: 这是一些示例输出:

          _/rrrr
        _/ \_/rrrlr
       / \   \rrrll
     _/   \_/rrlr
    / \     \rrll
   /   \   _/rlrr
  /     \_/ \rlrl
_/        \_/rllr
 \          \_/rlllr
  \           \rllll
   \        _/lrrr
    \     _/ \lrrl
     \   / \_/lrlr
      \_/    \lrll
        \   _/llrr
         \_/ \llrl
           \_/lllr
             \_/llllr
               \lllll

And a bit more asymmetric one that shows, perhaps, why I don't pad lines with spaces to the left until the end (via flatten ). 还有一点不对称的,也就是说,为什么我不填充左边的空格直到结尾(通过flatten )。 If the lower half had been padded on the left some of the upper arm would cross the padded area. 如果下半部分被填充在左侧,则一些上臂将穿过衬垫区域。

               _/rrrrr
             _/ \rrrrl
           _/ \rrrl
         _/ \_/rrlr
        / \   \rrll
       /   \_/rlr
      /      \rll
     /        /lrrr
    /       _/  _/lrrlrr
   /       / \_/ \lrrlrl
  /       /    \lrrll
_/      _/     _/lrlrrr
 \     / \   _/ \lrlrrl
  \   /   \_/ \lrlrl
   \_/      \lrll
     \      _/llrrr
      \   _/ \llrrl
       \_/ \llrl
         \lll

It's the "obvious" recursive algorithm - the devil is in the details. 这是“明显的”递归算法 - 魔鬼在细节中。 It was easiest to write without the "_", which makes the logic slightly more complex. 没有“_”这是最容易编写的,这使得逻辑稍微复杂一些。

Perhaps the only "insight" is gap_above = l_above - that's saying that the right "arm" has the length of the right side of the left subtree (you'll need to read that a few times). 也许唯一的“见解”是gap_above = l_above - 这就是说右边的“arm”具有左子树右侧的长度(你需要阅读几次)。 It makes things relatively balanced. 它使事情相对平衡。 See the asymmetric example above. 请参阅上面的非对称示例。

A good way of understanding things in more detail is to modify the pad routine to take a character instead of ' ' and give a different character for each call. 更详细地理解事物的一种好方法是修改pad例程以获取字符而不是' '并为每个调用赋予不同的字符。 Then you can see exactly which logic generated which space. 然后你可以确切地看到哪个逻辑生成了哪个空格。 This is what you get using A. B, C and D for the calls to pad from top to bottom, above (obviously there's no character when the amount of space is zero): 这是你使用A. B,C和D从上到下调用​​填充(当空间量为零时显然没有字符):

             _/rrrr
            / \rrrl
          _/B _/rrlrr
         / \_/ \rrlrl
        /AA  \rrll
      _/BBB  _/rlrrr
     / \DD _/ \rlrrl
    /AA \_/ \_/rlrlr
   /AAAA  \C  \rlrll
  /AAAAAA  \_/rllr
_/AAAAAAAA   \rlll
 \DDDDDDDD   _/lrrrr
  \DDDDDD  _/ \lrrrl
   \DDDD  / \lrrl
    \DD _/B _/lrlrr
     \_/ \_/ \lrlrl
       \C  \lrll
        \_/llr
          \lll

There's more explanation here (although the tree is very slightly different). 还有更多的解释在这里 (虽然树是非常略有不同)。

Make a representation structure, involving a string array and a line number of the "petal". 创建一个表示结构,涉及字符串数组和“花瓣”的行号。

rep(leaf) is [0, repr(leaf value)] rep(叶子)是[0,repr(叶子值)]

rep(nonleaf), given top = nonleaf.left and bottom = nonleaf.right : rep(nonleaf),给出top = nonleaf.leftbottom = nonleaf.right

Pad each line of rep(top) with spaces if above top's petal, or with slash at an appropriate position if below. 如果在顶部的花瓣上方,则在每个代表行(顶部)填充空格,如果在下面,则在适当的位置填充斜线。 Similarly, pad each line of rep(bottom) with spaces if below bottom's petal, or with backslash at an appropriate position if above. 类似地,如果在下面的花瓣下方,则用空格填充每行rep(底部),如果在上面,则在适当位置使用反斜杠。 repr(nonleaf) is then [height of top, padded lines of top followed by padded lines of bottom]. 然后是repr(非叶子)[顶部的顶部,顶部的填充线条,然后是底部的填充线条]。

Example: 例:

rep(a): [0, ["a"]]
rep(b): [0, ["b"]]
rep(ab): [1, ["/"+"a", "\"+"b"]]
rep(c): [0, ["c"]]
rep(d): [0, ["d"]]
rep(cd): [1, ["/"+"c", "\"+"d"]]
rep(e): [0, ["e"]]
rep(cde): [2, [" "+"/c", "/" + "\d", "\" + "e"]]
rep(abcde): [2, [" "+"/a", "/"+"\b", "\ "+" /c", " \" + "/\d", "  " + "\e"]]

Note that in top case, the width of the padding is the number of lines below petal; 注意,在大写的情况下,填充的宽度是花瓣下面的行数; in the bottom case, the width of the padding corresponds to petal. 在底壳中,衬垫的宽度对应于花瓣。 Thus, in (abcde) case, top has 2 lines and petal 1, so padding is (2 - 1 == 1) one character; 因此,在(abcde)情况下,top有2行和petal 1,所以padding是(2 - 1 == 1)个字符; bottom has petal 2, so padding is 2 characters. 底部有花瓣2,所以填充是2个字符。

If you want, you could also add an "_" at each nonleaf at (petal-1)th line (and an extra space to all other lines). 如果需要,您还可以在(第1行)第(1)行(以及所有其他行的额外空格)的每个非空白处添加“_”。

Obviously, none of this is code ("\\" is a syntax error waiting to happen), but it should not be too difficult to implement from here. 显然,这些都不是代码(“\\”是等待发生的语法错误),但从这里实现起来应该不会太难。

You need to approach this recursively, and track the sizes of the individual subtrees. 您需要递归地处理此问题,并跟踪各个子树的大小。 In particular, where the root is. 特别是,根在哪里。 A non-balanced tree can easily look like this: 非平衡树很容易看起来像这样:

/
\/
 \/
  \/
   \

Now consider you already have built this tree, what do you need to transform this to the following when adding the parent level. 现在考虑您已经构建了这个树,在添加父级时,您需要将其转换为以下内容。

  /
 /\/
/  \/
\   \/
 \   \
  \

The key idea is to start with the leaves. 关键的想法是从树叶开始。 They are trivial. 它们是微不足道的。 Then define a way to aggregate two subtrees, given they have a different amount of lines and a different position of the subtree root node. 然后定义聚合两个子树的方法,因为它们具有不同的行数和子树根节点的不同位置。

Here is a nice sideways tree that just helped me in debugging a project: http://www.acooke.org/cute/ASCIIDispl0.html 这是一个很好的横向树,它帮助我调试项目: http//www.acooke.org/cute/ASCIIDispl0.html

Results resemble the directory layout of the VIM NERDtree plugin if you've seen that. 如果您已经看到,结果类似于VIM NERDtree插件的目录布局。

Here is my re-implementation as a __str__ method in a binary tree: 这是我在二叉树中重新实现的__str__方法:

def __str__(self):
    """Recursive __str__ method of an isomorphic node."""
    # Keep a list of lines
    lines = list()
    lines.append(self.name)
    # Get left and right sub-trees
    l = str(self.left).split('\n')
    r = str(self.right).split('\n')
    # Append first left, then right trees
    for branch in l, r:
        # Suppress Pipe on right branch
        alt = '| ' if branch is l else '  '
        for line in branch:
            # Special prefix for first line (child)
            prefix = '+-' if line is branch[0] else alt
            lines.append(prefix + line)
    # Collapse lines
    return '\n'.join(lines)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM