问题 heapq.nlargest如何工作?


我在看 这个pycon谈话,34:30 并且发言者说得到了 t 列表中最大的元素 n 元素可以完成 O(t + n)

怎么可能?我的理解是创建堆将是 O(n),但是复杂性是多少? nlargest 本身就是这样 O(n + t) 要么 O(t) (什么是实际的算法)?


1827
2018-04-13 03:24


起源

你可能感兴趣 源代码。 - lvc
如果你想按排序顺序,显然这不会发生在线性时间。否则,你可以打电话 nlargest 同 t=n 比较按线性时间排序列表。如果你只是想要的话 t 中最大的元素 任何 顺序,可以在O(n)中完成 quickselect。 heapq.nlargest 但是,不使用quickselect;它使用基于堆的算法按排序顺序提供项目。 - user2357112
只是一个普遍的说明:声称需要时间O(t + n)本身让我感到警惕,因为那只是O(n)。这在技术上并不正确,但以某种方式表达它有点奇怪 - Niklas B.


答案:


在这种情况下,发言者是错误的。实际成本是 O(n * log(t))。仅在第一个时调用Heapify t 可迭代的元素。那是 O(t)但如果是微不足道的话 t 比小得多 n。然后将所有剩余的元素添加到此“小堆”中 heappushpop, 一次一个。这需要 O(log(t)) 每次调用的时间 heappushpop。堆的长度仍然存在 t 始终。在最后,堆被分类,这是成本 O(t * log(t)),但如果这也是微不足道的 t 比小得多 n

有趣的理论;-)

有很简单的方法可以找到预期的最大元素 O(n) 时间;例如, 看这里。在最坏的情况下,有更难的方法 O(n) 时间。然后,在输入的另一个传递中,您可以输出 t 元素> =第t大(在重复的情况下有繁琐的并发症)。整个工作 能够 完成 O(n) 时间。

但这些方式需要 O(n) 记忆也。 Python不使用它们。实际实现的优点是最坏情况下的“额外”内存负担 O(t)当输入是例如产生很多值的发生器时,这可能是非常重要的。


11
2018-04-13 03:35



伟大的,有道理;我真的很希望 O(t + n) 虽然是对的,我以为我会学习一些新的堆魔法:) - foo
现在看一下O(n)方法的编辑 - 但它与堆无关,唉。 - Tim Peters
有趣的事实:你 能够 事实上,在O(n)中堆积数组,并在每个查询的O(k)时间内获取结果堆的top-k。尽管如此,这是非常重要的 heapq 模块没有实现它。 (它也可能有巨大的常数因素,使其在实践中不可行) - Niklas B.
@NiklasB。哪里可以读到这个 O(k) 算法?即使非平凡我也非常感兴趣! - foo
@foo stackoverflow.com/questions/22574580/... - Niklas B.