堆
堆
世界上目前有很多种堆,比如二叉堆、斐波那契堆、配对堆、左 偏树等等,不过由于各种原因,OI里面提到的堆都是二叉堆,也 就是下面要讲的。下文的堆都是指的二叉堆。堆可以在单次严格O(logn)的时间复杂度内 插入一个数字、删除最大/小的数字,并严格O(1)询问最大/小的数字、
看起来,堆能做的事情非常有限,实际上,堆的应用十分广泛,它可以用来优化图论中的问题,比如Dijkstra堆优化、Prim堆优化等,堆还可以用来排序,这就是常见的堆排序,堆排序是一个不稳定排序,时间复杂度是稳定的O(log2n)。
关于堆的实现,手写堆的常数更低,会快一点,但是那很难写(和线段树的模板差不多长),在STL中,有现成的堆用,干嘛还要手写呢。
堆排序是很好写的,我愿意封他为“除了sort()以外最好用的排序”,下面是一个从大到小的堆排序模板:
#include<bits/stdc++.h> using namespace std; int a,ans[100],n; priority_queue<int> h; int main() { cin>>n; for(int i=1; i<=n; i++) cin>>a,h.push(a);//输入并入堆 for(int i=1; i<=n; i++) ans[i]=h.top(),h.pop();//入堆后,自动排序,再每次取堆头即可。 for(int i=1; i<=n; i++) cout<<ans[i]<<' '; return 0; }
STL建立堆的方式很简单,所谓堆,实际上就是优先队列
priority_queue<Type> h;
这就建好了一个优先队列,它是一个大根堆,如果想开一个小根堆,那么就稍微有一点长:
priority_queue<Type,vector<Type>,greater<Type> >
注:这里的Type是类型,什么类型都行,可以是关键字类型(如int,long long,char),class类型,用户自定义类型(结构体)。
STL的堆支持以下操作:
h.push(x);//往堆里插入x元素。 h.top()//堆头元素。 h.pop()//弹出堆头。 h.size()//堆内元素的个数。
说完了,上例题:P3871 [TJOI2010]中位数
就是让你要么插入一个数,要么输出当前序列的中位数,中位数是指将一个序列按照从小到大排序后处在中间位置的数。
我们首先将所有的数丢进大根堆里然后取一般丢进小根堆里,这样就把所有的数分成了两段有序的部分。
加入操作可以每次取小根堆堆顶和加入的数比较,大于堆顶则加入小根堆否则加入大根堆(为了维护这个排序的有序性)。
当询问时输出大根堆中的堆顶元素即可。
代码:
#include<bits/stdc++.h> using namespace std; priority_queue<int,vector<int>,greater<int> > xiao; priority_queue<int> da; string cz; int n,m,aa,a; int main() { cin>>n; for(int i=1; i<=n; i++) cin>>aa,da.push(aa); for(int i=1; i<=(n>>1); i++) xiao.push(da.top()),da.pop(); cin>>m; while(m--) { cin>>cz; if(cz=="add") { cin>>a,n++; if(a>da.top()) xiao.push(a); else da.push(a); } else { while(da.size()<(n+1>>1)) da.push(xiao.top()),xiao.pop(); while(da.size()>(n+1>>1)) xiao.push(da.top()),da.pop(); cout<<da.top()<<endl; } } return 0; }