Skip to content

Latest commit

 

History

History
144 lines (100 loc) · 3.99 KB

heapq.md

File metadata and controls

144 lines (100 loc) · 3.99 KB

heapq

堆结构,类似于 Queue 库里的 PriorityQueue 优先队列,其实 PriorityQueue 就是小顶堆,查看源代码可以发现优先队列的实现就是调用了 heapq 的函数。

堆一般是由树组成,具体来说是完全二叉树,类似二叉搜索树,父子节点之间有大小关系。

小顶堆的父节点的值小于子节点,在 优先队列中插入新值的时候,会将其与父节点的大小比较,如果小于父节点,则与之对换,直至根节点。

小顶堆的查询时间复杂度是 O(logK), K 为二叉树深度。

在 Queue 中的队列都是使用 python 的 列表实现的,在堆这里也是。

一个元素的下标为 i,其父节点的小标为 (i-1)/2

同样的,一个元素的下标为n,其子节点的位置分别为 (2*n)+1(2*n)+2,存在 Q[n] <= Q[2*n+1]Q[n] <= Q[2*n+2]

heapify

在 O(N) 的时间复杂度里,将一个列表转为小顶堆,Q[0] 为最小值,原地修改。

>>> from heapq import heapify
>>> a = [4,6,7,1,5,7,9,10,3,2]
>>> heapify(a)
>>> a
[1, 2, 7, 3, 4, 7, 9, 10, 6, 5]

merge

合并 k 个列表,返回排序后的结果。列表原始值可以为未排序值。

其实是实现了堆排序。

>>> from heapq import merge
>>> a = [4,6,7,1,5,7,9,10,3,2]
>>> b = [1,2,2,3,5,7,9,10,11]
>>> list(merge(a,b))
[1, 2, 2, 3, 4, 5, 6, 7, 1, 5, 7, 7, 9, 9, 10, 3, 2, 10, 11]

heappush

往一个堆里插入值。

操作顺序是,往堆的末位插入一个节点,然后将其与父节点的大小比较,如果小于父节点,则与之对换,直至根节点。

>>> a = [1, 2, 7, 3, 4, 7, 9, 10, 6, 5]
>>> from heapq import heappush
>>> heappush(a, 8)
>>> a
[1, 2, 7, 3, 4, 7, 9, 10, 6, 5, 8]

heappop

取出堆里最小值。

操作顺序是,先取走堆顶元素,然后将堆尾最后一个元素放至堆顶。然后将其与子节点的大小相比较,如果大于子节点,则与较小的那个子节点对换,直至叶节点。

>>> from heapq import heappop
>>> a = [1, 2, 7, 3, 4, 7, 9, 10, 6, 5]
>>> heappop(a)
1
>>> a
[2, 3, 7, 5, 4, 7, 9, 10, 6]

heapreplace

取出堆中最小元素,并将新值插入堆顶。

>>> a
[1, 2, 7, 3, 4, 7, 9, 10, 6, 5, 8]
>>> a = [1, 2, 7, 3, 4, 7, 9, 10, 6, 5]
>>> from heapq import heapreplace
>>> heapreplace(a, 6)
1
>>> a
[2, 3, 7, 6, 4, 7, 9, 10, 6, 5]

heappushpop

将堆顶与插入值做对比,返回最小值。

如果堆顶是教小值,返回堆顶元素。则插入教小值。如果插入值是教小值,则堆结构不变,返回教小值。

>>> a
[2, 3, 7, 6, 4, 7, 9, 10, 6, 5]
>>>
>>> from heapq import heappushpop
>>> heappushpop(a, 1)
1
>>> a
[2, 3, 7, 6, 4, 7, 9, 10, 6, 5]
>>> heappushpop(a, 3)
2
>>> a
[3, 3, 7, 6, 4, 7, 9, 10, 6, 5]

nsmallest

使用堆结构从列表中找出最小的n个元素

>>> from heapq import nsmallest
>>> a
[3, 3, 7, 6, 4, 7, 9, 10, 6, 5]
>>> nsmallest(4, a)
[3, 3, 4, 5]

很神奇的操作,构造最小的大根堆,每次构造大根堆出来之后,将最大值丢弃,保留最小值在堆里。最后在返回时调用排序算法排序输出。

可能也有一定的道理,在完成 n 个元素的大根堆之后,最大值在嘴上面,如果新插入的值大于最大值,则直接丢弃。如果插入的值小于最大值,将其插入运算。

如果构建的是 n 个元素的小根堆,则每次新的元素过来,都需要将其插入堆尾,最后最小的 n 个元素还要从堆头往下数 n 个。

nlargest

使用堆结构从列表中找出最大的n个元素

>>> from heapq import nlargest
>>> a = [3, 3, 7, 6, 4, 7, 9, 10, 6, 5]
>>> nlargest(4, a)
[10, 9, 7, 7]

其上函数都可以通过阅读源码习得。

源码中 _siftdown 为从子节点往上冒泡,做插入堆的函数,往父节点比较。 _siftup 为父节点往下冒泡,做取数的函数,往子节点做比较,在换为叶节点后还会再调用一次 _siftdown 做一次向上冒泡的校验。