欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 手游 > 数据结构——对顶堆

数据结构——对顶堆

2025/7/6 3:58:25 来源:https://blog.csdn.net/2301_80475191/article/details/144322039  浏览:    关键词:数据结构——对顶堆

对顶堆

由一个大根堆和一个小根堆组成,小根堆里面的数永远比大根堆里面的数要大

用途:用于动态维护区间内第k大的数,要比线段树和动态平衡树写起来更简单

 比如说我们要维护第k大的数,那么我们肯定是将前k大的数放进小根堆,小根堆的堆顶就是第k大的数

我们来看一下维护第k大的数的代码

1.建立大根堆和小根堆

priority_queue<int> qmax;  //相当于大顶堆
priority_queue<int,vector<int>,greater<int>> qmin;  //相当于小顶堆

2.如果小根堆是空的,或者说当前元素大于小根堆的堆顶元素,那么就将当前元素存入小根堆,否则就存入大根堆 

		if(qmin.empty()||a>=qmin.top()){qmin.push(a);}else{qmax.push(a);}

3.大、小根堆的维护,当我们小根堆的元素,不满k个,就将大根堆的堆顶元素弹入小根堆,如果我们当前小根堆的元素超过了k个,就将小根堆的堆顶元素摊入大根堆 

		while(qmin.size()>k){qmax.push(qmin.top());qmin.pop();}while(qmin.size()<k&&!qmax.empty()){qmin.push(qmax.top());qmax.pop();}

4.查询第k大的元素,访问小根堆的堆顶元素

cout<<qmin.top()<<"\n";

 例题

P1168 中位数

思路:去求奇数项的中位数,这个中位数的下标是在不断变化的,很板的一种动态维护第k大的元素的板题,直接套用对顶堆代码即可

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,k;
int a;
priority_queue<int> qmax;
priority_queue<int,vector<int>,greater<int> > qmin;
signed main()
{cin>>n;for(int i=1;i<=n;i++){cin>>a;if(i%2==1){k=(i+1)/2;}if(qmin.empty()||a>=qmin.top()){qmin.push(a);}else{qmax.push(a);}while(qmin.size()>k){qmax.push(qmin.top());qmin.pop();}while(qmin.size()<k&&!qmax.empty()){qmin.push(qmax.top());qmax.pop();}if(i%2==1){cout<<qmin.top()<<"\n";}}return 0;
}

 P7072 [CSP-J2020] 直播获奖

 思路:第一眼看上去,获奖分数线为多少?即i*w/100位置上的人的分数是多少,翻译过来还是第k大的元素是多少,动态维护第k大的对顶堆板题

直接写就行

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,k;
double w;
int a;
priority_queue<int>qmax;
priority_queue<int,vector<int>,greater<int> >qmin;
signed main()
{cin>>n>>w;for(int i=1;i<=n;i++){cin>>a; int z=floor(i*w/100);k=max(1LL,z);if(qmin.empty()||a>=qmin.top()){qmin.push(a);}else{qmax.push(a);}while(qmin.size()<k&&!qmax.empty()){qmin.push(qmax.top());qmax.pop();}while(qmin.size()>k){qmax.push(qmin.top());qmin.pop();}cout<<qmin.top()<<" ";}return 0;
} 

P2085 最小函数值

 思路:我们发现a,b,c都是正数,那么其堆成周-b/2*a,永远都在负数的位置,也就是说,这n个函数,在0~正无穷内都是单调递增的,那么我们可以考虑一种剪枝的O(n*m)的写法,我们首先创建一个大顶堆,存储前k小的元素,我们在最外层遍历n个函数,内层遍历m个数,当n为1的时候,先将m个函数值存进去,然后当后续的函数,如果存在比堆顶元素小的,就存进大顶堆,弹出堆顶

,如果比堆顶元素大,那就立刻结束当前内层循环,因为后面也会比堆顶元素大,。

直到双重循环遍历完,然后输出倒序前m个即可

//P2085 最小函数值
#include<bits/stdc++.h>
using namespace std;
#define int long longint n,m;
priority_queue<int>qmax;
int a,b,c;
int f;
int ans[200005];
signed main()
{cin>>n>>m;for(int i=1;i<=n;i++){cin>>a>>b>>c;for(int j=1;j<=m;j++){f=a*j*j+b*j+c;if(i==1){qmax.push(f);}else{if(f<qmax.top()){qmax.pop();qmax.push(f);}else{break;}}}}for(int i=1;i<=m;i++){ans[i]=qmax.top();qmax.pop();}for(int i=m;i>=1;i--){cout<<ans[i]<<" ";}return 0;
}

P1631 序列合并

思路:和上面那个一个思路,我们写一个有剪枝的O(n^2)的写法 ,和上面那个思路差不多

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int a[100005];
int b[100005];
int ans[100005];
priority_queue<int> q;
signed main()
{cin>>n;for(int i=1;i<=n;i++)cin>>a[i];for(int i=1;i<=n;i++)cin>>b[i];sort(a+1,a+1+n);sort(b+1,b+1+n);int k;for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){k=a[i]+b[j];if(i==1){q.push(k);}else{if(k<q.top()){q.pop();q.push(k);}else{break;}}}}for(int i=1;i<=n;i++){ans[i]+=q.top();q.pop();}for(int i=n;i>=1;i--)cout<<ans[i]<<" ";return 0;
}

P1801 黑匣子

思路:我们很容易发现,题目还是动态求第k小的,因此我们要将前k小的,放入大根堆中,当我们大根堆位空,或者说当前元素小于大根堆的堆顶,就存进来,我们用vis[i]去统计要进行输出的位置即可

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int a[200005];
int b[200005];
int vis[200005];
priority_queue<int> qmax;
priority_queue<int,vector<int>,greater<int> > qmin;signed main()
{cin>>n>>m;for(int i=1;i<=n;i++){cin>>a[i];}for(int i=1;i<=m;i++){cin>>b[i];vis[b[i]]++;}int k=0;for(int i=1;i<=n;i++){if(qmax.empty()||a[i]<qmax.top()){qmax.push(a[i]);}else{qmin.push(a[i]);}while(vis[i]){k++;while(qmax.size()<k){qmax.push(qmin.top());qmin.pop();}while(qmax.size()>k){qmin.push(qmax.top());qmax.pop();}cout<<qmax.top()<<"\n";vis[i]--;}}return 0;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com