使用yield的树遍历的时间复杂度是多少?

时间:2016-12-10 19:56:20

标签: python time-complexity yield

深度优先树遍历的示例:

class Node:
    def __init__(self, value):
        self._value = value
        self._children = []

    def add_child(self, child):
        self._children.append(child)

    def __iter__(self):
        return iter(self._children)

    def depth_first(self):
        yield self
        for c in self:
            yield from c.depth_first()

我知道yield from不会立即使用生成器,而是将yield向上传递给其调用者。

传递的过程仍然存在,因此yield将从每个节点一直传递到其根节点,我们可以通过重复来描述运行时间(假设它是一个简单的二叉树,但想法是相同的):

  

T(n)= 2 * T(n / 2)+Θ(n)

Θ(n)存在,因为此节点必须将从其后代传递的所有yield传递给其父节点。从上面的公式得出的时间复杂度是:

  

O(nlogn)

但是,如果我根本不使用O(n)yield,则树遍历的时间复杂度仅为yield from

我想知道我是否误解了yield是如何工作的,或者编写这样的递归生成器是不可行的。

1 个答案:

答案 0 :(得分:1)

来自yield from的官方Python 3.3版本:https://www.python.org/dev/peps/pep-0380/

  

使用专门的语法为优化提供了可能性   当有一长串发电机时。这样的链可能会出现   例如,递归遍历树结构时。开销   传递 next ()调用并在链中向上和向上产生值   在最糟糕的情况下,可能导致应该成为O(n)操作   案例,O(n ** 2)。

     

可能的策略是向生成器添加一个槽   用于保存委托给的生成器的对象。当下一个()或   send()调用是在生成器上进行的,首先检查此插槽,然后   如果它是非空的,它引用的生成器将恢复   代替。如果它引发StopIteration,则清空槽和主槽   发电机恢复。

     

这会将委托开销减少到a   C函数调用链,不涉及Python代码执行。一个   可能的增强将是遍历整个链   但是,循环中的生成器会直接恢复最后的生成器   StopIteration的处理比较复杂。

看起来yield from仍然需要遍历树。但是,遍历是由C语言中的解释器而不是Python完成的。从技术上讲,它仍然是O(n)开销,但它没有听起来那么糟糕。