在Python中实现深度优先树迭代器

时间:2014-10-01 16:06:55

标签: python algorithm tree iterator depth-first-search

我正在尝试在Python中为不一定二进制树实现迭代器类。在使用树的根节点构造迭代器之后,可以重复调用其next()函数以按深度优先顺序遍历树(例如,this order),最后返回None时没有节点。

以下是树的基本Node类:

class Node(object):

    def __init__(self, title, children=None):
        self.title = title
        self.children = children or []
        self.visited = False   

    def __str__(self):
        return self.title

正如您在上面所看到的,我为节点引入了visited属性,这是我的第一种方法,因为我没有看到解决方法。通过额外的状态衡量,Iterator类看起来像这样:

class Iterator(object):

    def __init__(self, root):
        self.stack = []
        self.current = root

    def next(self):
        if self.current is None:
            return None

        self.stack.append(self.current)
        self.current.visited = True

        # Root case
        if len(self.stack) == 1:
            return self.current

        while self.stack:
            self.current = self.stack[-1] 
            for child in self.current.children:
                if not child.visited:
                    self.current = child
                    return child

            self.stack.pop()

这一切都很好,但是我想摆脱对visited属性的需求,而不需要对Node类进行递归或任何其他更改。

我需要的所有状态都应该在迭代器中处理,但我不知道如何做到这一点。保留整个树的访问列表是不可扩展的,而且不可能,因此必须有一种聪明的方法来使用堆栈。

特别让我感到困惑的是 - 由于next()函数当然会返回,我怎么能记住我在没有标记任何内容或使用多余存储空间的情况?直觉上,我想到循环遍历孩子,但当next()函数返回时,该逻辑被破坏/遗忘!

更新 - 这是一个小测试:

tree = Node(
    'A', [
        Node('B', [
            Node('C', [
                Node('D')
                ]),
            Node('E'),
            ]),
        Node('F'),
        Node('G'),
        ])

iter = Iterator(tree)

out = object()
while out:
    out = iter.next()
    print out

2 个答案:

答案 0 :(得分:7)

如果你真的必须避免递归,这个迭代器可以工作:

from collections import deque

def node_depth_first_iter(node):
    stack = deque([node])
    while stack:
        # Pop out the first element in the stack
        node = stack.popleft()
        yield node
        # push children onto the front of the stack.
        # Note that with a deque.extendleft, the first on in is the last
        # one out, so we need to push them in reverse order.
        stack.extendleft(reversed(node.children))

话虽如此,我认为你很难想到这一点。一个好的' (递归)生成器也可以解决问题:

class Node(object):

    def __init__(self, title, children=None):
        self.title = title
        self.children = children or []

    def __str__(self):
        return self.title

    def __iter__(self):
        yield self
        for child in self.children:
            for node in child:
                yield node

这两个都通过了你的测试:

expected = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
# Test recursive generator using Node.__iter__
assert [str(n) for n in tree] == expected

# test non-recursive Iterator
assert [str(n) for n in node_depth_first_iter(tree)] == expected

如果您愿意,可以轻松地使Node.__iter__使用非递归表单:

def __iter__(self):
   return node_depth_first_iter(self)

答案 1 :(得分:0)

  

但是,这仍然可能包含每个标签。我想要   迭代器一次只保留树的一个子集。

但是你已经 拿着一切。请记住,对象本质上是一个字典,每个属性都有一个条目。在self.visited = False __init__Node表示您为每个"visited"对象存储了多余的False密钥和Node值,无论。至少,一个集合也有可能保存每个节点ID。试试这个:

class Iterator(object):
    def __init__(self, root):
        self.visited_ids = set()
        ...

    def next(self):
        ...
        #self.current.visited = True
        self.visited_ids.add(id(self.current))
        ...
                #if not child.visited:
                if id(child) not in self.visited_ids:

查找集合中的ID应该与访问节点的属性一样快。这可能比你的解决方案更浪费的唯一方法是set对象本身(而不是它的元素)的开销,如果你有多个并发迭代器(你显然没有,否则节点{{1),这只是一个问题。 }}属性对你没用。)