Python 使用收益率遍历树的时间复杂度是多少?
深度优先树遍历的示例:Python 使用收益率遍历树的时间复杂度是多少?,python,time-complexity,yield,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
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)
之所以存在,是因为该节点必须将从其子节点传递给其父节点的所有产量。由上述公式得出的时间复杂度为:
O(非登录)
然而,如果我根本不使用yield
或yield from
,则树遍历的时间复杂度仅为O(n)
我想知道我是否误解了yield
的工作原理,或者像这样编写递归生成器根本不可行。来自官方Python 3.3版本的yield From
:
使用专门的语法可以进行优化
当有一长串发电机时。例如,这样的链条可能会出现
实例,递归遍历树结构时。头顶
传递next()调用,并在链上下产生值
在最坏的情况下,可能会导致本应是O(n)操作的情况发生
案例,O(n**2)
一种可能的策略是向生成器添加插槽
对象来保存要委派给的生成器。当一个下一个()或
在生成器上进行send()调用,首先检查此插槽,然后
如果它是非空的,则恢复它引用的生成器
相反如果它引发StopIteration,则插槽将被清除,主
发电机恢复运行
这将把委派开销减少到一个新的水平
不涉及Python代码执行的C函数调用链。A.
可能的增强措施是遍历整个数据链
发电机在一个回路中,并直接恢复到末端的发电机,尽管
StopIteration的处理则更为复杂
看起来,yield from
仍然需要遍历树。但是这种遍历是由C语言的解释器而不是Python语言完成的。所以从技术上讲,这仍然是一个O(n)开销,但它并不像听起来那么糟糕 你的公式中的Θ(n)不应该是Θ(1)吗?平衡树中节点的后代数不是常数吗?@DYZ我认为应该是Θ(n)。它不传递子代的数量,但传递所有子代的产量
语句。“所有子代”是否包括子代的子代,等等。?(我假设不是。)如果不是,它仍然是一个常数。@DYZ它确实包括子节点的子节点,因为每个节点将做两件事:(1)将自己让给调用方(2)让给它的子节点和它的子节点让给它的子节点,等等……哦,你是对的。实际上是O(n log n),因为您实际上是将遍历结果委托给根节点,而不是在本地使用它们。这是一个已解决的问题,还是他们仍在处理它?