C++数据结构·堆
堆 ( h e a p ) (heap) (heap)数据结构详解
堆是一种特殊的完全二叉树,常用于实现优先队列和堆排序算法。堆分为两种主要类型:最大堆和最小堆。
堆的基本特性
最大堆 ( M a x (Max (Max H e a p ) Heap) Heap)
- 每个节点的值都大于或等于其子节点的值
- 根节点是堆中的最大值
最小堆 ( M i n (Min (Min H e a p ) Heap) Heap)
- 每个节点的值都小于或等于其子节点的值
- 根节点是堆中的最小值
堆的表示
堆通常使用数组来表示,利用完全二叉树的特性:
- 对于索引为
i的节点:- 父节点索引: ( i − 1 ) / 2 (i-1)/2 (i−1)/2
- 左子节点索引: 2 ∗ i + 1 2*i+1 2∗i+1
- 右子节点索引: 2 ∗ i + 2 2*i+2 2∗i+2
时间复杂度
| 操作 | 时间复杂度 |
|---|---|
| 插入 | O ( l o g O(log O(log n ) n) n) |
| 删除 | O ( l o g O(log O(log n ) n) n) |
| 获取最大/最小值 | O ( 1 ) O(1) O(1) |
| 构建堆 | O ( n ) O(n) O(n) |
堆是一种高效的数据结构,特别适合需要频繁访问最大或最小元素的场景
模板展示
#include <bits/stdc++.h>
using namespace std;
priority_queue<int,vector<int>>q;//大根堆
priority_queue<int,vector<int>,greater<int>>Q;//小根堆
//加了greater就是小根堆
int main(){
//增加元素操作:
q.push(1);
//删除堆顶
q.pop();
//堆的长度
int a=q.size();
cout <<a<<endl;
//堆顶
q.push(1);
q.push(2);
q.push(3);
cout <<q.top();//堆顶
return 0;
}
代码展示了大根堆与小根堆的定义和操作
结果将会输出
0
3
若是用Q完成操作,将会输出
0
1
即大根堆的堆顶是最大值,小根堆的堆顶是最小值
每次使用 p o p pop pop会弹出堆顶
这里给出手写堆的示例:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+5;
int h[MAXN],len=0;//len记录当前二叉树的长度
void push_s(int x){//上浮,插入新元素
h[++len]=x;
int i=len;
while(i>1&&h[i]<h[i/2]){
swap(h[i],h[i/2]);
i=i/2;
}
}
void pop_s(){//下沉,删除堆头,调整堆
h[1]=h[len--];//根结点替换为最后一个结点,然后结点数量减1
int i=1;
while(2*i<=len){//至少有左儿子
int son=2*i;//左儿子
if(son<len&&h[son+1]<h[son])son++;//son<len表示有右儿子,选儿子中较小的
if(h[son]<h[i]){//与小的儿子交换
swap(h[son],h[i]);
i=son;//下沉到儿子处
}
else break;//如果不比儿子小,就停止下沉
}
}//_s小根堆
void push_b(int x){//下沉,插入新元素
h[++len]=x;
int i=len;
while(i>1&&h[i]>h[i/2]){//改为大于号
swap(h[i],h[i/2]);
i=i/2;
}
}
void pop_b(){//上浮,删除堆头,调整堆
h[1]=h[len--];//根结点替换为最后一个结点,然后结点数量减1
int i=1;
while(2*i<=len){//至少有左儿子
int son=2*i;//左儿子
if(son<len&&h[son+1]>h[son])son++;//改为大于号,选较大的儿子
if(h[son]>h[i]){//改为大于号,与大的儿子交换
swap(h[son],h[i]);
i=son;//下沉到儿子处
}
else break;//如果不比儿子大,就停止下沉
}
}//_b大根堆
int main(){
return 0;
}
调用部分自己参考
主要功能介绍 : \large\color{FFC500}{主要功能介绍:} 主要功能介绍:
- p u s h push push_ s s s
小根堆的 p u s h push push,塞入一个元素 x x x并上浮 - p o p pop pop_ s s s
小根堆的 p o p pop pop,弹出堆头并下沉 - p u s h push push_ b b b
大根堆的 p u s h push push,塞入一个元素 x x x并下沉 - p u s h push push_ b b b
大根堆的 p o p pop pop,弹出堆头并上浮
例题
P1090 [NOIP 2004 提高组] 合并果子
题目背景
P6033 为本题加强版。
题目描述
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n − 1 n-1 n−1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 1 1 1 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有 3 3 3 种果子,数目依次为 1 1 1 , 2 2 2 , 9 9 9 。可以先将 1 1 1 、 2 2 2 堆合并,新堆数目为 3 3 3 ,耗费体力为 3 3 3 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12 12 12 ,耗费体力为 12 12 12 。所以多多总共耗费体力 = 3 + 12 = 15 =3+12=15 =3+12=15 。可以证明 15 15 15 为最小的体力耗费值。
输入格式
共两行。
第一行是一个整数 n ( 1 ≤ n ≤ 10000 ) n(1\leq n\leq 10000) n(1≤n≤10000) ,表示果子的种类数。
第二行包含 n n n 个整数,用空格分隔,第 i i i 个整数 a i ( 1 ≤ a i ≤ 20000 ) a_i(1\leq a_i\leq 20000) ai(1≤ai≤20000) 是第 i i i 种果子的数目。
输出格式
一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 2 31 2^{31} 231 。
输入输出样例 #1
输入 #1
3
1 2 9
输出 #1
15
说明/提示
对于 30 % 30\% 30% 的数据,保证有 n ≤ 1000 n \le 1000 n≤1000;
对于 50 % 50\% 50% 的数据,保证有 n ≤ 5000 n \le 5000 n≤5000;
对于全部的数据,保证有 n ≤ 10000 n \le 10000 n≤10000。
例题分析
可以分析
假如有 a , b , c a,b,c a,b,c三堆果子
即合并 a a a与 b b b后,还需合并 a + b a+b a+b和 c c c
则尽量使 a + b a+b a+b最小
即满足 a ≤ b ≤ c a\leq{b}\leq{c} a≤b≤c
那么联想到小根堆(即把堆顶2个数合并)
代码实现如下
AC代码1
直接调用函数
这里注意 x , y x,y x,y要分开,先弹出堆顶再得到 y y y再弹出
注意: s . s i z e ( ) s.size() s.size()需 ≥ 2 ≥2 ≥2
因为需要弹出两次
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
priority_queue<int,vector<int>,greater<int>>s;
int a[MAXN];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
s.push(a[i]);
}
int ans=0;
while(s.size()>1){
int x=s.top();
s.pop();
int y=s.top();
s.pop();
ans+=x+y;
s.push(x+y);
}cout <<ans;
return 0;
}
AC代码2
用手写堆
注意是从 h [ 1 ] h[1] h[1]开始计算的
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+5;
int h[MAXN],len=0;
int a[MAXN],sum=0;
void push(int x){
h[++len]=x;
int i=len;
while(i>1&&h[i]<h[i/2]){
swap(h[i],h[i/2]);
i=i/2;
}
}
void pop(){
h[1]=h[len--];
int i=1;
while(2*i<=len){
int son=2*i;
if(son<len&&h[son+1]<h[son])son++;
if(h[son]<h[i]){
swap(h[son],h[i]);
i=son;
}
else break;
}
}
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
push(a[i]);
}
while(len>1){
int x=h[1];
pop();
int y=h[1];
pop();
sum+=x+y;
push(x+y);
}cout <<sum;
return 0;
}
题单推荐
题单和例题来自 洛谷 洛谷 洛谷
~ 完结撒花 完结撒花 完结撒花 ~
附:仅展示模板,习惯使用 M A X N MAXN MAXN作为数组最大空间
推荐 洛谷 洛谷 洛谷作为你的刷题区域
下一篇预告:奇妙的代码实现?或者其他数据结构或算法
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)