【问题标题】:What's the time complexity of functions in heapq libraryheapq 库中函数的时间复杂度是多少
【发布时间】:2016-12-12 20:53:58
【问题描述】:

我的问题来自下面leetcode中的解决方案,我不明白为什么是O(k+(n-k)log(k))

补充:可能复杂度不是这个,其实我不知道heappush()heappop()的时间复杂度

# O(k+(n-k)lgk) time, min-heap
def findKthLargest(self, nums, k):
    heap = []
    for num in nums:
        heapq.heappush(heap, num)
    for _ in xrange(len(nums)-k):
        heapq.heappop(heap)
    return heapq.heappop(heap)

【问题讨论】:

  • @ValentinLorentz,我相信lgx 一般是指log(x)
  • 我们需要更多的上下文。你了解heappush()heappop()的时间复杂度吗?你明白第 4 行和第 5 行的循环是低效的,而且整个例程确实没有必要的效率吗?
  • 不是。有一种相当简单的方式来使用堆,给出了声明的O() 复杂性,但是这个特定的代码并不接近。
  • @RoryDaulton 好吧,我不知道 heappush() 和 heappop() 的时间复杂度。我在任何地方都找不到它们...
  • 那么在回答这个问题之前,您需要学习priority queueheap 以及binary tree 的概念。如果名为heap 的堆大小为n,则heappush()heappop() 的复杂度为O(log(n))。这是因为堆在概念上是一个完整的二叉树,大约有 log(n) 个级别。

标签: python heap


【解决方案1】:

heapq 是一个二进制堆,具有 O(log n) push 和 O(log n) pop。请参阅heapq source code

您展示的算法需要 O(n log n) 将所有项目推送到堆上,然后 O((n-k) log n) 来找到第 k 个最大的元素。所以复杂度是 O(n log n)。它还需要 O(n) 额外空间。

您可以在 O(n log k) 中执行此操作,通过稍微修改算法使用 O(k) 额外空间。我不是 Python 程序员,所以你必须翻译伪代码:

# create a new min-heap
# push the first k nums onto the heap
for the rest of the nums:
    if num > heap.peek()
        heap.pop()
        heap.push(num)

# at this point, the k largest items are on the heap.
# The kth largest is the root:

return heap.pop()

这里的关键是堆只包含迄今为止看到的最大项目。如果一个项目小于迄今为止看到的第 k 个最大的项目,它永远不会被放入堆中。最坏的情况是 O(n log k)。

实际上,heapq 有一个 heapreplace 方法,所以你可以替换它:

    if num > heap.peek()
        heap.pop()
        heap.push(num)

    if num > heap.peek()
        heap.replace(num)

此外,推送第一个k 项的替代方法是创建第一个k 项的列表并调用heapify。更优化(但仍为 O(n log k))的算法是:

# create array of first `k` items
heap = heapify(array)
for remaining nums
    if (num > heap.peek())
        heap.replace(num)
return heap.pop()

您也可以在整个数组上调用heapify,然后弹出第一个n-k 项,然后取顶部:

heapify(nums)
for i = 0 to n-k
    heapq.heappop(nums)
return heapq.heappop(nums)

这更简单。不确定它是否比我之前的建议更快,但它修改了原始数组。复杂度是 O(n) 来构建堆,然后是 O((n-k) log n) 来构建堆。所以它是 O((n-k) log n)。最坏情况 O(n log n)。

【讨论】:

  • 我刚回来是因为我记得发错了。我对此进行了测试,heapify 更快(需要 80% 的时间在相同的输入上)。但是在 sorted(thelist) 中使用直接索引比任何一个都快得多。
  • @KennyOstrom:最后一个选项最快也就不足为奇了。如果 OP 可以修改原始数组,那么他可能应该使用那个。
  • 对于所有测量,我使用了单独复制数组的版本。例如 heap=nums[:]; heapify(堆)
  • @user2361174:因为在一般情况下,'(n-k)log n' 项将使 O(n) 项相形见绌。
  • @wakeup 从稀疏数据(在 1000 个项目的堆上运行三个)很难说,但似乎对 heappop 的改进大约为 42%。所以复杂度将是 O(0.58 * log n))。这仍然被认为是 O(log n)。您必须使用更大的 n 进行更详尽的测试,以查看 0.58 常数是否成立。
【解决方案2】:

heapify() 实际上需要线性时间,因为该方法与调用 heapq.push() N 次不同。

heapq.push()/heapq.pop() 需要 log n 时间,因为它会调整给定高度/级别的所有节点。

当你在 heapify() 中传递一个数组时,它会确保节点的左右子节点已经在维护堆属性,无论它是最小堆还是最大堆。

你可以看到这个视频: https://www.youtube.com/watch?v=HqPJF2L5h9U

https://www.youtube.com/watch?v=B7hVxCmfPtM

希望这会有所帮助。

【讨论】:

  • 请尽量不要在上面发链接并提供解决方案代码sn-ps,考虑添加视频链接作为最后的选择,考虑到那些有视力障碍的人
【解决方案3】:

从@Shivam purbia 的帖子中总结:

  1. 使用heaps.heapify() 可以减少时间空间的复杂性,因为heaps.heapify()an in-place heapify and costs linear time to run it
  2. heapq.heappush()heapq.heappop() 都花费 O(logN) 时间复杂度

最终的代码会是这样的……

import heapq

def findKthLargest(self, nums, k):
    heaps.heapify(nums)            # in-place heapify -> cost O(N) time
    
    for _ in range(len(nums)-k):   # run (N-k) times
        heapq.heappop(heap)        # cost O(logN) time
    return heapq.heappop(heap)     
  • 总时间复杂度为 O((N - k)logN)
  • 总空间复杂度为 O(1)

【讨论】:

  • 对我帮助很大!
【解决方案4】:

仅用于创建和堆积元素,它是 O(nlogn)。 但是对于只是 heapify 元素,它是 o(n)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-11-02
    • 1970-01-01
    • 2020-12-03
    • 2017-09-11
    • 2020-03-13
    • 1970-01-01
    相关资源
    最近更新 更多