Yield all root-to-leaf branches of a Binary Tree

淺唱寂寞╮ 提交于 2021-01-27 14:50:22

问题


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 traversemethod 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!