【问题标题】:Yield all root-to-leaf branches of a Binary Tree产生二叉树的所有从根到叶的分支
【发布时间】:2019-08-20 19:24:58
【问题描述】:

抱歉,这是一个常见问题,但我还没有为我的特定问题找到合适的答案。我正在尝试实现一个walk 方法,该方法将二叉树从其根节点遍历到其每个叶节点,每当我到达叶节点时都会产生从根到叶的路径。例如,遍历表示的二叉树:

     __a__
    /     \
   b       d
  / \     / \
 -   c   -   -

会产生:

['a', 'b', 'c']
['a', 'd']

我的想法是BinaryTree.walk在根节点上调用Node.traverse,然后递归调用每个子节点的traverse方法。 BinaryTree.walk 还会创建一个空列表,每个 traverse 调用都会传递该列表,附加每个节点的数据,一旦到达叶节点就产生列表,并在访问每个节点后将每个元素弹出列表。

在某些时候,有些事情出了问题。这是我的代码:

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)

预期输出:

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)

实际输出:

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

我知道我对列表做错了,因为它试图在它为空时弹出,但我不明白它是怎么做到的。对于每个append 调用,它应该调用一次pop

我也无法弄清楚为什么节点被输入和退出,但 ON NODE: 消息没有被打印...就像我的代码只是以某种方式跳过了 child.traverse(branch=branch) 行?

谁能帮我理解我在哪里搞砸了?

提前感谢您的帮助!

【问题讨论】:

    标签: python python-3.x data-structures binary-tree


    【解决方案1】:

    有一个很好的答案here

    复制他们的 Python 示例:

    """ 
    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. 
    
    

    给:

    10 8 3
    10 8 5
    10 2 2

    你也可以像你一样给它写字母:

    root = Node("A") 
    root.left = Node("B") 
    root.right = Node("D") 
    root.left.right = Node("C") 
    printPaths(root) 
    

    给:

    A B C
    一个D

    【讨论】:

    • 感谢您的回答!我在搜索答案时找到了这个链接,实际上我设法在我的案例中实现了这段代码,但我并没有真正理解它在做什么。感觉辅助功能太多了……?还有几个变量,比如pathLen,我不确定这有什么意义。我希望使用yield 语句以更“直截了当”的方式(至少从我的角度来看)实现。如果我不明白我的例子有什么问题,我肯定会再给它一次机会。再次感谢:)
    【解决方案2】:

    这是您的代码的修改变体。

    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()
    

    注意事项

    • 我稍微重构了代码(简化,更改了一些标识符名称、文本和其他无关紧要的更改)
    • children 属性:
      • None 对于节点的 leftright 属性,表示该节点没有子节点,因此在返回的结果中没有意义
      • 由于问题涉及yield,我将它变成了一个生成器(而不是返回一个元组或列表,...)。因此,我不得不添加 is_leaf,因为生成器不会评估为 False(即使为空)

    输出

    [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)
    


    你的代码有什么问题?

    这是 traverse 循环调用 (child.traverse(branch=branch))。 它创建了一个生成器,但由于它没有在任何地方使用(迭代),该函数实际上并没有调用自己,导致尝试删除的元素多于添加的元素(仅 1:根节点)。
    所以,事实证明你几乎就在那里。您所要做的就是在它前面添加一个 yield from :)。
    更多详情请关注[Python]: PEP 380 -- Syntax for Delegating to a Subgenerator

    【讨论】:

    • 非常感谢 - 这正是我所需要的!很好的答案,解释和例子。正如您所说,只需添加yield from,它就可以完美地工作,尽管我肯定也会重构一下——我喜欢您的children 属性,这是有道理的。我现在还不能奖励赏金,但我会在等待期结束后立即奖励,你已经赚到了! :)
    • 不客气!不要担心赏金,这不是我选择这个问题的原因(尽管如果没有赏金,我可能会跳过它,因为没有人可以监控所有问题),这对我来说是一个练习还有!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-25
    • 2021-09-29
    • 1970-01-01
    相关资源
    最近更新 更多