Урожай все корневые ветви бинарного дерева - PullRequest
3 голосов
/ 29 марта 2019

Извините, если это общий вопрос, но я не нашел подходящего ответа для своей конкретной проблемы. Я пытаюсь реализовать метод 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

Я понимаю, что делаю что-то не так со списком, так как он пытается выскочить, когда он пуст, но я не могу понять, как он дошел до этого. Он должен вызывать pop один раз для каждого append вызова.

Также я не могу понять, почему узлы вводятся и выходят, но сообщение ON NODE: не печатается ... Как будто мой код просто как-то пропускает строку child.traverse(branch=branch)?

Может кто-нибудь помочь мне понять, где я все испортил?

Заранее спасибо за помощь!

Ответы [ 2 ]

1 голос
/ 01 апреля 2019

Вот модифицированный вариант вашего кода.

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

Примечания

  • Я немного переработал код (упростили, изменил некоторые имена идентификаторов, тексты и другие незначительные изменения)
  • дети собственность:
    • Нет для атрибута left или right узла означает, что у узла нет дочернего элемента, поэтому нет смысла включать его в возвращаемый результат
    • Поскольку вопрос касается 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)


Что не так с вашим кодом?

Это повтор повторяющийся вызов (child.traverse(branch=branch)). Он создал генератор, но поскольку он нигде не использовался (повторялся), функция фактически не вызывала себя , что приводило к попытке удалить больше элементов, чем добавлено (только * 1056). * 1 : это корневой узел).
Итак, оказывается, что вы были почти там. Все, что вам нужно сделать, это добавить yield from перед ним :).
Подробнее о [Python]: PEP 380 - Синтаксис для делегирования субгенератору .

1 голос
/ 29 марта 2019

Здесь отличный ответ здесь

Копирование их примера 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) 

Дает:

ABC
AD

...