问题
I have a python code to convert a string mathematical expression into a binary tree and order the nodes of the tree so the left child will be always smaller than the right child. I want to print the binary tree in the following order.
For example consider the mathematical expression ((2 * 75) / 4). buildParseTree() converts the string expression into a tree and printNodeInLevels() rearranges the nodes so left child is smaller than the right right child at each level. Operands < operators and operators are orders as '+' < '-' < '*' < '/'. If the structure of the tree is like this
+
/\
4 *
/\
2 75
I want to print it as follows. How should I go about this? Because the length of the mathematical expressions vary all the time e.g (24 * 2), ((5 - 1) * (2 / 3)), (20 - (5 + 4)) etc
Node("+") #root
.addkid(Node("*") #right child at level 1
.addkid(Node("75")) #right child at level 2
.addkid(Node("2")) #left child at level 2
)
.addkid(Node("4")) #left child at level 1
I have worked out the method to print nodes by their levels as in an in-order traversal pattern.If I call the method as follows it will print the following:
pt = buildParseTree("( ( 2 * 74 ) / 4 )")
printNodesInLevels(pt)
output:
/
4 *
2 74
回答1:
Here is a function I created to print any binary tree structure.
It is very generic and only needs a starting node (root) and a function (or lambda) to obtain a label and the left/right children nodes:
You would typically use it like this on your Node class:
printBTree(rootNode,lambda n: (n.operand, n.left, n.right) )
# assuming the Node class has a string property named operand
# and left,right properties that return a Node or None
A quadratic equation (-b +/- sqrt(b**2 - 4*a*c))/(2*a) could then print like this:
# /
# ___/ \__
# +/- *
# / \ / \
# - sqrt 2 a
# \ \
# b -
# __/ \_
# ** *
# / \ / \
# b 2 4 *
# / \
# a c
Here is the printBTree function :
import functools as fn
def printBTree(node, nodeInfo=None, inverted=False, isTop=True):
# node value string and sub nodes
stringValue, leftNode, rightNode = nodeInfo(node)
stringValueWidth = len(stringValue)
# recurse to sub nodes to obtain line blocks on left and right
leftTextBlock = [] if not leftNode else printBTree(leftNode,nodeInfo,inverted,False)
rightTextBlock = [] if not rightNode else printBTree(rightNode,nodeInfo,inverted,False)
# count common and maximum number of sub node lines
commonLines = min(len(leftTextBlock),len(rightTextBlock))
subLevelLines = max(len(rightTextBlock),len(leftTextBlock))
# extend lines on shallower side to get same number of lines on both sides
leftSubLines = leftTextBlock + [""] * (subLevelLines - len(leftTextBlock))
rightSubLines = rightTextBlock + [""] * (subLevelLines - len(rightTextBlock))
# compute location of value or link bar for all left and right sub nodes
# * left node's value ends at line's width
# * right node's value starts after initial spaces
leftLineWidths = [ len(line) for line in leftSubLines ]
rightLineIndents = [ len(line)-len(line.lstrip(" ")) for line in rightSubLines ]
# top line value locations, will be used to determine position of current node & link bars
firstLeftWidth = (leftLineWidths + [0])[0]
firstRightIndent = (rightLineIndents + [0])[0]
# width of sub node link under node value (i.e. with slashes if any)
# aims to center link bars under the value if value is wide enough
#
# ValueLine: v vv vvvvvv vvvvv
# LinkLine: / \ / \ / \ / \
#
linkSpacing = min(stringValueWidth, 2 - stringValueWidth % 2)
leftLinkBar = 1 if leftNode else 0
rightLinkBar = 1 if rightNode else 0
minLinkWidth = leftLinkBar + linkSpacing + rightLinkBar
valueOffset = (stringValueWidth - linkSpacing) // 2
# find optimal position for right side top node
# * must allow room for link bars above and between left and right top nodes
# * must not overlap lower level nodes on any given line (allow gap of minSpacing)
# * can be offset to the left if lower subNodes of right node
# have no overlap with subNodes of left node
minSpacing = 2
rightNodePosition = fn.reduce(lambda r,i: max(r,i[0] + minSpacing + firstRightIndent - i[1]), \
zip(leftLineWidths,rightLineIndents[0:commonLines]), \
firstLeftWidth + minLinkWidth)
# extend basic link bars (slashes) with underlines to reach left and right
# top nodes.
#
# vvvvv
# __/ \__
# L R
#
linkExtraWidth = max(0, rightNodePosition - firstLeftWidth - minLinkWidth )
rightLinkExtra = linkExtraWidth // 2
leftLinkExtra = linkExtraWidth - rightLinkExtra
# build value line taking into account left indent and link bar extension (on left side)
valueIndent = max(0, firstLeftWidth + leftLinkExtra + leftLinkBar - valueOffset)
valueLine = " " * max(0,valueIndent) + stringValue
slash = "\\" if inverted else "/"
backslash = "/" if inverted else "\\"
uLine = "¯" if inverted else "_"
# build left side of link line
leftLink = "" if not leftNode else ( " " * firstLeftWidth + uLine * leftLinkExtra + slash)
# build right side of link line (includes blank spaces under top node value)
rightLinkOffset = linkSpacing + valueOffset * (1 - leftLinkBar)
rightLink = "" if not rightNode else ( " " * rightLinkOffset + backslash + uLine * rightLinkExtra )
# full link line (will be empty if there are no sub nodes)
linkLine = leftLink + rightLink
# will need to offset left side lines if right side sub nodes extend beyond left margin
# can happen if left subtree is shorter (in height) than right side subtree
leftIndentWidth = max(0,firstRightIndent - rightNodePosition)
leftIndent = " " * leftIndentWidth
indentedLeftLines = [ (leftIndent if line else "") + line for line in leftSubLines ]
# compute distance between left and right sublines based on their value position
# can be negative if leading spaces need to be removed from right side
mergeOffsets = [ len(line) for line in indentedLeftLines ]
mergeOffsets = [ leftIndentWidth + rightNodePosition - firstRightIndent - w for w in mergeOffsets ]
mergeOffsets = [ p if rightSubLines[i] else 0 for i,p in enumerate(mergeOffsets) ]
# combine left and right lines using computed offsets
# * indented left sub lines
# * spaces between left and right lines
# * right sub line with extra leading blanks removed.
mergedSubLines = zip(range(len(mergeOffsets)), mergeOffsets, indentedLeftLines)
mergedSubLines = [ (i,p,line + (" " * max(0,p)) ) for i,p,line in mergedSubLines ]
mergedSubLines = [ line + rightSubLines[i][max(0,-p):] for i,p,line in mergedSubLines ]
# Assemble final result combining
# * node value string
# * link line (if any)
# * merged lines from left and right sub trees (if any)
treeLines = [leftIndent + valueLine] + ( [] if not linkLine else [leftIndent + linkLine] ) + mergedSubLines
# invert final result if requested
treeLines = reversed(treeLines) if inverted and isTop else treeLines
# return intermediate tree lines or print final result
if isTop : print("\n".join(treeLines))
else : return treeLines
Here's an example of the kind of output it produces, using a simple TreeNode class.
class TreeNode:
def __init__(self,rootValue):
self.value = rootValue
self.left = None
self.right = None
def addValue(self,newValue):
if newValue == self.value: return self
if newValue < self.value:
if self.left : return self.left.addValue(newValue)
self.left = TreeNode(newValue)
return self.left
if self.right : return self.right.addValue(newValue)
self.right = TreeNode(newValue)
return self.right
def printTree(self):
printBTree(self,lambda n:(str(n.value),n.left,n.right))
root = TreeNode(80)
root.addValue(50)
root.addValue(90)
root.addValue(10)
root.addValue(60)
root.addValue(30)
root.addValue(70)
root.addValue(55)
root.addValue(5)
root.addValue(35)
root.addValue(85)
root.printTree()
This produces the following output:
# 80
# ___/ \___
# 50 90
# __/ \__ /
# 10 60 85
# / \ / \
# 5 30 55 70
# \
# 35
The function is generic enough to process binary tree structures that are not stored in an object hierarchy. Here's an example of how it can be used to print from a list containing a heap tree:
def printHeapTree(tree, inverted=False):
def getNode(index):
left = index * 2 + 1
right = index * 2 + 2
left = left if left < len(tree) and tree[left] else None
right = right if right < len(tree) and tree[right] else None
return (str(tree[index]), left, right)
printBTree(0,getNode,inverted)
formula = ["+","4","*",None,None,"2","75"]
printHeapTree(formula)
# +
# / \
# 4 *
# / \
# 2 75
The function will automatically adjust the indentations for wider labels :
family = [ "Me","Paul","Rosa","Vincent","Jody","John","Kate"]
printHeapTree(family)
# Me
# ___/ \___
# Paul Rosa
# / \ / \
# Vincent Jody John Kate
It can also print the tree upside down (as would be appropriate for a family tree):
printHeapTree(family,inverted=True)
# Vincent Jody John Kate
# \ / \ /
# Paul Rosa
# ¯¯¯\ /¯¯¯
# Me
回答2:
Well to begin with you should read PEP8 code convention for python, as it says function, attributes and variables should be in snake_case.
You are printing in an iterative way so that means you just can't print it in an isosceles triangle because you cannot know what is the size of the base (the lowest part of the tree), in an iterative way you should print it like a triangle with 90 degrees angle.
Or you could gather all of the information into a lists or a strings and format that later and print it. Think about the head and then the children with lines between them.
回答3:
A simple and rough one:
from collections import deque
def print_tree(root):
res = []
q = deque([root])
while q:
row = []
for _ in range(len(q)):
node = q.popleft()
if not node:
row.append("#")
continue
row.append(node.val)
q.append(node.left)
q.append(node.right)
res.append(row)
rows = len(res)
base = 2**(rows)
for r in range(rows):
for v in res[r]:
print("." * (base), end = "")
print(v, end = "")
print("." * (base - 1), end = "")
print("|")
base //= 2
print_tree(root)
来源:https://stackoverflow.com/questions/48850446/how-to-print-a-binary-tree-in-as-a-structure-of-nodes-in-python