深度优先树遍历的示例:
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
是如何工作的,或者编写这样的递归生成器是不可行的。
答案 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)开销,但它没有听起来那么糟糕。