手动实现最小堆和最大堆(优先队列)

Posted kachunyippp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手动实现最小堆和最大堆(优先队列)相关的知识,希望对你有一定的参考价值。

当我们要在一组数据中找到最小/大值或者前K大/小值的时候,我们可以使用传统的遍历方法。那么这个时候时间复杂度就是$O(N^2)$,但我们可以使用"堆"来进行优化,我们可以把找到最小/大值的复杂度降低到$O(logN)$。插入一个新值的复杂度也是$O(logN)$。

 

维护一个堆关键的就是向下维护和向上维护,基于这两种方法我们就可以实现插入,删除

向下调整,时间复杂度:$O(logn)$

建堆,时间复杂度:$O(n)$

技术分享图片
 1 //建立大根堆 
 2 
 3 const int maxn = 100;
 4 //heap为堆,n为元素个数
 5 int heap[maxn],n=10;
 6 
 7 void downAdjust(int low,int high)
 8 {
 9     int i = low, j = i*2;
10     while(j <= high)
11     {
12         if(j+1<=high && heap[j+1]>heap[j])
13             ++j;
14         if(heap[j] > heap[i])
15         {
16             swap(heap[j],heap[i]);
17             i = j;
18             j = i*2;    
19         }
20         else
21             break;    
22     }
23 } 
24 
25 void createHeap()
26 {
27     for(int i=n/2;i>=1;--i)
28     {
29         downAdjust(i,n);
30     }
31 }
View Code

 

完全二叉树的叶子结点个数为$iggllceilfrac{n}{2}iggr ceilqquad$,因此数组下标在$[1,iggllfloorfrac{n}{2}iggr floor]$范围内的结点都是非叶子结点。于是可以从$iggllfloorfrac{n}{2}iggr floor]$号位开始倒着枚举结点。倒着枚举保证了每个结点都是以其为根节点的子树中权值最大的结点。

删除堆顶元素 时间复杂度:$O(logn)$

技术分享图片
 1 void deleteTop() // 删除堆顶元素 时间复杂度:O(logn)
 2 {
 3     heap[1] = heap[n--];
 4     downAdjust(1,n);
 5 }
 6 向上调整,时间复杂度:O(logn)
 7 void upAdjust(int low,int high)
 8 {
 9     int i = high,j = i/2;
10     while(j>=low)
11     {
12         if(heap[j] < heap[i])
13         {
14             swap(heap[j],heap[i]);
15             i = j;
16             j = i/2;
17         }
18         else
19             break;
20     }
21 }
22 
23 void insert(int x) // 添加元素
24 {
25     heap[++n] = x;
26     upAdjust(1,n);
27 }
View Code

 

堆排序
具体实现时,为了节省空间,可以倒着遍历数组,假设当前访问到i号位,那么将堆顶元素与i号位的元素交换,接着在[1,i-1]范围内对堆顶元素进行一次向下调整

技术分享图片
1 void heapSort()
2 {
3     createHeap(); // 建堆
4     for(int i=n;i>1;--i)
5     {
6         swap(heap[i],heap[1]);
7         downAdjust(1,i-1);    
8     } 
9 }
View Code

 

以上的就是最大堆的实现,最小堆只要把判断大小换过来便是了

Dijkstra算法也可以使用堆来优化找离源点最近的顶点的过程,使时间复杂度下降到$O(M+N)logN$,堆还可以用来求一个数列中第K大的数:首先建立一个大小为K的最小堆,从第K+1个数开始,与堆顶进行比较,如果比堆顶大则代替堆顶并维护,比堆顶小则直接舍弃。这样最后堆顶便是第K大数。时间复杂度为$O(NlogK)$。


以上是关于手动实现最小堆和最大堆(优先队列)的主要内容,如果未能解决你的问题,请参考以下文章

python 实现堆和堆排序

LeetCode 295 数据流的中位数[队列] HERODING的LeetCode之路

优先级队列实现

如何用 Python 实现堆和优先队列?

算法导论笔记——第六七章 堆排序和快速排序

线性表--08---优先队列