如何在其侧面打印二叉树,以便输出看起来像这样?
__/a
__/ \b
\ _/c
\_/ \d
\e
(更漂亮的ascii-art welcome)
以下是一些不太有用的代码:
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,"")
哪个输出:
_/hg19
_/ \rheMac2
_/ \mm9
/\_/bosTau4
/ \_/canFam2
_/ \pteVam1
\_/loxAfr3
\dasNov2
范围蔓延:如果您可以传入一个函数来返回字符串以打印任何节点,那将是非常好的;通过这种方式,我有时也可以打印有关非离开节点的信息。因此,节点是否有要打印的内容由作为参数传入的函数控制。
以下是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
}
}
}
答案 0 :(得分:7)
这是一些实现其他地方描述的通用递归方法的代码。树的内部表示是子节点的字符串(叶子)或元组(对)。节点的中间“片段”的内部表示是元组(above, below, lines)
,其中above
和below
是根上下的行数,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))
以下是一些示例输出:
_/rrrr
_/ \_/rrrlr
/ \ \rrrll
_/ \_/rrlr
/ \ \rrll
/ \ _/rlrr
/ \_/ \rlrl
_/ \_/rllr
\ \_/rlllr
\ \rllll
\ _/lrrr
\ _/ \lrrl
\ / \_/lrlr
\_/ \lrll
\ _/llrr
\_/ \llrl
\_/lllr
\_/llllr
\lllll
还有一个更不对称的,也就是说,为什么我不填充左边的空格直到结尾(通过flatten
)。如果下半部分被填充在左侧,则一些上臂将穿过衬垫区域。
_/rrrrr
_/ \rrrrl
_/ \rrrl
_/ \_/rrlr
/ \ \rrll
/ \_/rlr
/ \rll
/ /lrrr
/ _/ _/lrrlrr
/ / \_/ \lrrlrl
/ / \lrrll
_/ _/ _/lrlrrr
\ / \ _/ \lrlrrl
\ / \_/ \lrlrl
\_/ \lrll
\ _/llrrr
\ _/ \llrrl
\_/ \llrl
\lll
这是“明显的”递归算法 - 魔鬼在细节中。最简单的是没有“_”,这使得逻辑稍微复杂一些。
也许唯一的“见解”是gap_above = l_above
- 这就是说正确的“手臂”具有左子树右侧的长度(你需要阅读几次)。它使事情相对平衡。请参阅上面的非对称示例。
更详细地理解事物的一种好方法是修改pad
例程以取代字符而不是' '
,并为每个调用赋予不同的字符。然后你可以确切地看到哪个逻辑生成了哪个空格。这就是使用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
还有更多解释here(尽管树略有不同)。
答案 1 :(得分:2)
创建一个表示结构,涉及字符串数组和“花瓣”的行号。
rep(叶子)是[0,repr(叶子值)]
rep(nonleaf),给定top = nonleaf.left
和bottom = nonleaf.right
:
如果位于顶部的花瓣上方,则在每个代表行(顶部)填充空格,如果在下方,则在适当的位置填充斜线。类似地,如果在下面的花瓣下方,则用空格填充每行rep(底部),如果在上面,则在适当位置使用反斜杠。然后是repr(nonleaf)[顶部的顶部,顶部的填充线,然后是底部的填充线。)
示例:
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"]]
注意,在大写的情况下,填充的宽度是花瓣下面的行数;在底壳中,衬垫的宽度对应于花瓣。因此,在(abcde)情况下,top有2行和petal 1,所以padding是(2 - 1 == 1)个字符;底部有花瓣2,所以填充是2个字符。
如果你愿意,你也可以在(第1行)第(1)行(以及所有其他行的额外空格)的每个非空白处添加“_”。
显然,这些都不是代码(“\”是等待发生的语法错误),但从这里实现起来应该不会太难。
答案 2 :(得分:0)
您需要递归地处理此问题,并跟踪各个子树的大小。特别是,根在哪里。非平衡树很容易看起来像这样:
/
\/
\/
\/
\
现在考虑您已经构建了这个树,在添加父级别时需要将其转换为以下内容。
/
/\/
/ \/
\ \/
\ \
\
关键的想法是从树叶开始。它们是微不足道的。然后定义一种聚合两个子树的方法,因为它们具有不同的行数和子树根节点的不同位置。
答案 3 :(得分:0)
这是一个很好的横向树,它帮助我调试项目: http://www.acooke.org/cute/ASCIIDispl0.html
如果您已经看到,结果类似于VIM NERDtree插件的目录布局。
这是我在二叉树中重新实现为__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)