什么是Python的heapq模块?

时间:2013-11-14 13:54:17

标签: python data-structures heap python-module

我尝试"heapq"并得出结论,我的期望与我在屏幕上看到的不同。我需要有人解释它是如何工作的以及它在哪里有用。

2.2排序段落下的书Python Module of the Week开始,

  

如果在添加和删除值时需要维护已排序的列表,   看看heapq。通过使用heapq中的函数来添加或删除   列表中的项目,您可以维护列表的排序顺序   低开销。

以下是我的所作所为。

import heapq
heap = []

for i in range(10):
    heap.append(i)

heap
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

heapq.heapify(heap)    
heapq.heappush(heap, 10)    
heap
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

heapq.heappop(heap)
0    
heap
[1, 3, 2, 7, 4, 5, 6, 10, 8, 9] <<< Why the list does not remain sorted?

heapq.heappushpop(heap, 11)
1
heap
[2, 3, 5, 7, 4, 11, 6, 10, 8, 9] <<< Why is 11 put between 4 and 6?

因此,正如您所看到的那样,“堆”列表根本没有排序,实际上您添加和删除项目的次数越多,它就越混乱。推动价值取无法解释的位置。 到底是怎么回事?

4 个答案:

答案 0 :(得分:79)

heapq模块维护堆不变量,这与按实际排序顺序维护实际列表对象不同。

引用heapq documentation

  

堆是二叉树,每个父节点的值都小于或等于其任何子节点。此实现使用的数组heap[k] <= heap[2*k+1]heap[k] <= heap[2*k+2]用于所有k,从零开始计算元素。为了比较,不存在的元素被认为是无限的。堆的有趣属性是它的最小元素始终是根heap[0]

这意味着找到最小元素(只需要heap[0])非常有效,这对于优先级队列来说非常有用。之后,接下来的2个值将比第1个更大(或相等),之后的4个值将大于其“父”节点,然后接下来的8个更大等等。

您可以在Theory section of the documentation中详细了解数据结构背后的理论。您还可以观看this lecture from the MIT OpenCourseWare Introduction to Algorithms course,它可以概括地解释算法。

可以非常有效地将堆转回到排序列表中:

def heapsort(heap):
    return [heapq.heappop(heap) for _ in range(len(heap))]

只需从堆中弹出下一个元素即可。但是,使用sorted(heap)应该更快,因为Python排序使用的TimSort算法将利用堆中已经存在的部分排序。

如果您只对最小值或第一个n最小值感兴趣,则使用堆,特别是如果您持续对这些值感兴趣的话;添加新项目和删除最小项目确实非常有效,而不是每次添加值时都使用列表。

答案 1 :(得分:28)

你的书错了!正如你所展示的那样,堆不是排序列表(尽管排序列表是堆)。什么是堆?引用Skiena的算法设计手册

  

堆是一种简单而优雅的数据结构,用于有效支持优先级队列操作insert和extract-min。它们通过维持元素集上的部分顺序来工作,这些元素比排序顺序弱(因此它可以有效地维护)但比随机顺序更强(因此可以快速识别最小元素)。

与排序列表相比,堆遵循较弱的条件堆不变。在定义之前,首先要考虑为什么放松这种情况可能会有用。答案是较弱的条件是更容易维护。你可以用堆做得少,但你可以更快

堆有三个操作:

  1. 查找 - 最小值为O(1)
  2. 插入O(日志n)
  3. 删除 - 最小O(日志n)
  4. Crucially Insert是O(log n),它对于排序列表击败O(n)。

    什么是堆不变量? “父母主宰孩子的二元树”。也就是说,“p ≤ c适用于p的所有孩子c”。 Skiena用图片说明并继续演示插入元素的算法,同时保持不变量。如果你想一段时间,你可以自己发明它们。 (提示:它们被称为起泡和泡沫)

    好消息是,包含电池的Python在heapq模块中为您实现了一切。它没有定义堆类型(我认为它更容易使用),但是它们在列表中提供了帮助函数。

    道德:如果您使用排序列表编写算法但只从一端检查和删除,那么您可以通过使用堆来提高算法效率。

    对于堆数据结构有用的问题,请阅读https://projecteuler.net/problem=500

答案 2 :(得分:23)

对堆数据结构实现存在一些误解。 heapq模块实际上是binary heap实现的变体,其中堆元素存储在列表中,如下所述:https://en.wikipedia.org/wiki/Binary_heap#Heap_implementation

引用维基百科:

  

堆通常用数组实现。任何二叉树都可以存储在一个数组中,但由于二进制堆总是一个完整的二叉树,因此可以紧凑地存储它。指针不需要空间;相反,可以通过算术对数组索引找到每个节点的父节点和子节点。

下面的图片可以帮助您感受树和列表表示之间的区别,并且(注意,这是最大堆,这是通常的最小堆的反转! ):

enter image description here

通常,堆数据结构与排序列表的不同之处在于它牺牲了一些关于任何特定元素是否比任何其他元素更大或更小的信息。堆只能说,这个特殊元素比它的父母和更大的孩子要少。数据结构存储的信息越少,修改它所需的时间/内存就越少。比较堆和排序数组之间某些操作的复杂性:

        Heap                  Sorted array
        Average  Worst case   Average   Worst case

Space   O(n)     O(n)         O(n)      O(n)

Search  O(n)     O(n)         O(log n)  O(log n)

Insert  O(1)     O(log n)     O(n)      O(n)

Delete  O(log n) O(log n)     O(n)      O(n)

答案 3 :(得分:0)

我知道这是一个比较老的问题,但是OP只是错过了答案,并提供了图表和解释,说明了以线性方式列出排序顺序时为什么会出现排序顺序。

(所以我不打算进行优化,效率等。我在回答视觉问题,OP问题的结构)

他在pymotw.com上,但是如果他只能去: https://pymotw.com/2/heapq/

“最小堆要求父级小于或等于子级”

所以想想树,想想金字塔。

这也不是一个不好的链接 https://medium.com/basecs/learning-to-love-heaps-cef2b273a238

因此每个父母都有两个孩子的政策。孩子们也只能有两个孩子元素。

它的优点在于,孩子总是会小于或等于父母的(heap-max)或大于或等于父母的(heap min)。

heap-max或heap-min(会引起混淆)是指最顶部的元素,如果是线性的,则是指

堆[0]。是将最大值表示为开始还是将最小值表示为开始。

我将尽可能地省略数学。

所以(数字是索引)

heap [0]有两个孩子。堆[1]和堆[2]。

heap [1]孩子将是heap [3]和heap [4]

heap [2]孩子将是heap [5]和heap [6]

heap [3]孩子将是heap [7]和heap [8]

堆[4]个孩子将是堆[9]和堆[10]

,依此类推。

这个问题

[2, 3, 5, 7, 4, 11, 6, 10, 8, 9] <<< Why is 11 put between 4 and 6?

因为值11存储在索引5中,所以索引5是索引2的子项,索引2的值为3。值4(索引4)是索引1的子项

从最小的顺序开始,以线性方式检查时只是看不到。

parent -> child 

[0] -> [0] is 2
-
[0] -> [1] is 3
[0] -> [2] is 5
-
[1] -> [3] is 7
[1] -> [4] is 4
[2] -> [5] is 11  <-- between 4 and 6
[2] -> [6] is 6

所以...。这是真的。 “最小堆要求父级小于或等于子级”

让自己发疯,然后用铅笔写下来,直到最大。。。

(曾经写过其中一件东西,只是等着被博士后压榨吗?)

所以让我们弹出第一个元素,就像普通列表或队列一样

[0] -> [0] is 3
-
[0] -> [1] is 5
[0] -> [2] is 7
-
[1] -> [3] is 4
[1] -> [4] is 11  

停下来。

索引1的值为5。索引3,它的子级值为4,并且较小。对堆进行重新排序以维护关系。因此它基本上不会进行外观排序,并且看起来不会像在弹出值之前的自身迭代一样。

有一些方法可以对节点重新排序,第二篇文章讨论了如何对它们进行排序。我只是想具体回答这个问题。