问题
Sorry if this is a common question but I haven't found an appropriate answer for my particular problem. I'm trying to implement a walk
method that walks a binary tree from its root node to each of its leaf nodes, yielding the root-to-leaf path whenever I get to a leaf node. For example, walking the binary tree represented by:
__a__
/ \
b d
/ \ / \
- c - -
Would yield:
['a', 'b', 'c']
['a', 'd']
My idea is that BinaryTree.walk
calls Node.traverse
on the root node, which in turn calls the traverse
method of each child node recursively. BinaryTree.walk
also creates an empty list which is passed around with each traverse
call, appending the data of each node, yielding the list once a leaf node is reached and popping each element out of the list after visiting each node.
At some point, something is going wrong though. This is my code:
class Node:
def __init__(self, data=None, left=None, right=None):
self.data = data
self.left = left
self.right = right
def __repr__(self):
return f"{self.__class__.__name__}({self.data})"
@property
def children(self):
return self.left, self.right
def traverse(self, branch):
print('ON NODE:', self)
branch.append(self.data)
if self.left is None and self.right is None:
yield branch
else:
for child in self.children:
if child is not None:
print('ENTERING CHILD:', child)
child.traverse(branch=branch)
print('EXITING CHILD:', child)
branch.pop()
class BinaryTree:
def __init__(self, root=Node()):
if not isinstance(root, Node):
raise ValueError(f"Tree root must be Node, not {type(root)}")
self.root = root
def __repr__(self):
return f"{self.__class__.__name__}({self.root})"
def walk(self):
node = self.root
branch = []
yield from node.traverse(branch=branch)
if __name__ == '__main__':
# create root node
n0 = Node('A')
# create binary tree with root node
tree = BinaryTree(root=n0)
# create others nodes
n1 = Node(data='B')
n2 = Node(data='C')
n3 = Node(data='D')
# connect nodes
n0.left = n1
n0.right = n3
n1.right = n2
# walk tree and yield branches
for branch in tree.walk():
print(branch)
Expected output:
ON NODE: Node(A)
ENTERING CHILD: Node(B)
ON NODE: Node(B)
ENTERING CHILD: Node(C)
ON NODE: Node(C)
['A', 'B', 'C'] # yielded branch
EXITING CHILD: Node(C)
EXITING CHILD: Node(B)
ENTERING CHILD: Node(D)
ON NODE: Node(D)
['A', 'D'] # yielded branch
EXITING CHILD: Node(D)
Actual output:
ON NODE: Node(A)
ENTERING CHILD: Node(B)
EXITING CHILD: Node(B)
ENTERING CHILD: Node(D)
EXITING CHILD: Node(D)
IndexError: pop from empty list
I understand I'm doing something wrong with the list since it's trying to pop when it's empty, but I can't understand how it got to that. It should call pop
once for each append
call.
Also I can't figure out why the nodes are being entered and exited, but the ON NODE:
message is not being printed... It's like my code just skips the child.traverse(branch=branch)
line somehow?
Can anyone help me understand where I'm messing this up?
Thanks in advance for your help!
回答1:
Here's a modified variant of your code.
code.py:
#!/usr/bin/env python3
import sys
class Node:
def __init__(self, data=None, left=None, right=None):
self.data = data
self.left = left
self.right = right
def __repr__(self):
return f"{self.__class__.__name__}({self.data})"
@property
def children(self):
if self.left:
yield self.left
if self.right:
yield self.right
@property
def is_leaf(self):
return self.left is None and self.right is None
def traverse_preord(self, accumulator=list()):
print(" On node:", self)
accumulator.append(self.data)
if self.is_leaf:
yield accumulator
else:
for child in self.children:
print(" Entering child:", child)
yield from child.traverse_preord(accumulator=accumulator)
accumulator.pop()
print(" Exiting child:", child)
def main():
root = Node(data="A",
left=Node(data="B",
right=Node(data="C")
),
right=Node(data="D",
#left=Node(data="E"),
#right=Node(data="F"),
)
)
for path in root.traverse_preord():
print("Found path:", path)
if __name__ == "__main__":
print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
main()
Notes:
- I refactored the code a bit (simplified, changed some identifier names, texts and other insignificant changes)
- children property:
- None for a node's left or right attribute, means that the node has no child, so no point including it in the returned result
- Since the question is involving yield, I turned it into a generator (instead of returning a tuple or list, ...). As a consequence, I had to add is_leaf, since a generator doesn't evaluate to False (even if empty)
Output:
[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q055424449]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code.py Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] on win32 On node: Node(A) Entering child: Node(B) On node: Node(B) Entering child: Node(C) On node: Node(C) Found path: ['A', 'B', 'C'] Exiting child: Node(C) Exiting child: Node(B) Entering child: Node(D) On node: Node(D) Found path: ['A', 'D'] Exiting child: Node(D)
What is wrong with your code?
It's the traverse recurring call (child.traverse(branch=branch)
). It created a generator, but since that wasn't used (iterated on) anywhere, the function didn't actually called itself, resulting in the attempt of removing more elements than added (only 1: which is the root node).
So, it turns out that you were almost there. All you have to do, is adding a yield from
in front of it :).
More details on [Python]: PEP 380 -- Syntax for Delegating to a Subgenerator.
回答2:
There is a great answer here
Copying their Python example:
"""
Python program to print all path from root to
leaf in a binary tree
"""
# binary tree node contains data field ,
# left and right pointer
class Node:
# constructor to create tree node
def __init__(self, data):
self.data = data
self.left = None
self.right = None
# function to print all path from root
# to leaf in binary tree
def printPaths(root):
# list to store path
path = []
printPathsRec(root, path, 0)
# Helper function to print path from root
# to leaf in binary tree
def printPathsRec(root, path, pathLen):
# Base condition - if binary tree is
# empty return
if root is None:
return
# add current root's data into
# path_ar list
# if length of list is gre
if(len(path) > pathLen):
path[pathLen] = root.data
else:
path.append(root.data)
# increment pathLen by 1
pathLen = pathLen + 1
if root.left is None and root.right is None:
# leaf node then print the list
printArray(path, pathLen)
else:
# try for left and right subtree
printPathsRec(root.left, path, pathLen)
printPathsRec(root.right, path, pathLen)
# Helper function to print list in which
# root-to-leaf path is stored
def printArray(ints, len):
for i in ints[0 : len]:
print(i," ",end="")
print()
# Driver program to test above function
"""
Constructed binary tree is
10
/ \
8 2
/ \ /
3 5 2
"""
root = Node(10)
root.left = Node(8)
root.right = Node(2)
root.left.left = Node(3)
root.left.right = Node(5)
root.right.left = Node(2)
printPaths(root)
# This code has been contributed by Shweta Singh.
Gives:
10 8 3
10 8 5
10 2 2
You can give it letters like you have too:
root = Node("A")
root.left = Node("B")
root.right = Node("D")
root.left.right = Node("C")
printPaths(root)
Gives:
A B C
A D
来源:https://stackoverflow.com/questions/55424449/yield-all-root-to-leaf-branches-of-a-binary-tree