2.4 堆
概念
- 本质为一颗完全二叉树
- 以小根堆为例,其每个结点的值,均小于等于左右子节点,即根节点为整棵树的最小值
- 支持操作
- 插入一个数
- 求集合中的最小值
- 删除最小值
- 删除任意一个元素
- 修改任意一个元素
操作思想
- 存储方式:用一维数组存储,设根节点的下标是
i
,则左儿子是2*i
,右儿子是2*i+1
//对无序的一维数组进行建堆(小根堆)
for(int i=n/2;i;i--) down(i); //n表示数组大小
-
排序:若某结点的值发生改变,则判断其与父节点和左右两个子结点的大小关系,再进行上移或下移,递归处理直至满足当前堆的性质。
-
以小根堆为例:若某一结点的值增大,则需要将其向下移动,直到不能下移为止。
-
删除和修改元素:将最后插入的元素覆盖掉需要修改的元素,之后从修改元素的位置重新对堆进行排序
模板(注释解析版)
//以小根堆为例
const int N=1e6+10; //堆的大小
int idx,m; //idx表示当前的结点在数组中的下标,m表示插入的第m个数
int h[N],ph[N],hp[N];
//h[N]为堆
//ph[N]存储 第m个数 在h[N]中的下标 为idx 即ph[m]=idx
//hp[N]存储 在h[N]中 下标为idx的数 为插入第m个数 即hp[idx]=m
//由于我们在涉及到操作第m个元素时需要知道该元素是插入的第m个数,故需要p[h]和hp[N]的映射关系来确定
void h_swap(int a,int b){ //完成交换操作
swap(ph[hp[a]],ph[hp[b]]);
//通过hp[a]找到idx=a是插入的第m_i个数 ph[hp[a]]就可以得到其在h[N]中的下标
//通过hp[b]找到idx=a是插入的第m_j个数 ph[hp[b]]就可以得到其在h[N]中的下标
//交换idx的映射关系
swap(hp[a],hp[b]);
//交换m的映射关系
swap(h[a],h[b]);
//交换值
}
void down(int u){ //完成下移操作
//u表示父结点
int t=u; //t用于比较
int p=u*2; //p表示左儿子节点的下标 p+1表示右儿子
if(p<=idx&&h[p]<h[t]) t=p;
//p表示左儿子的下标,要在树的大小范围内,若左儿子小于父节点,则暂时记录t,最后与父节点u交换
if(p+1<=idx&&h[p+1]<h[t]) t=p+1;
//p+1表示右儿子的下标,要在树的大小范围内,若右儿子小于父节点,则暂时记录t,最后与父节点u交换
if(t!=u){ //若t与原来的u不相等,说明u需要下移
h_swap(t,u); //交换
down(t); //递归处理,直到不能下移
}
}
void up(int u){ //完成上移操作
//u表示子结点
int t=u>>1; //t表示u的父结点的下标
if(t&&h[u]<h[t]){ //当t>0并且子结点的值小于父结点的值,说明u要上移
h_swap(t,u); //交换
up(t); //递归处理,直到不能上移
}
}
模板(简注释)
//以小根堆为例
const int N=1e6+10; //堆的大小
int idx,m; //idx表示当前的结点在数组中的下标,m表示插入的第m个数
int h[N],ph[N],hp[N];
void h_swap(int a,int b){
swap(ph[hp[a]],ph[hp[b]]); //交换idx的映射关系
swap(hp[a],hp[b]); //交换m的映射关系
swap(h[a],h[b]); //交换值
}
void down(int u){
int t=u;
int p=u*2; //p表示左儿子节点的下标 p+1表示右儿子
if(p<=idx&&h[p]<h[t]) t=p;
if(p+1<=idx&&h[p+1]<h[t]) t=p+1;
if(t!=u){
h_swap(t,u);
down(t); //递归处理,直到不能下移
}
}
void up(int u){
int t=u>>1;
if(t&&h[u]<h[t]){
h_swap(t,u);
up(t); //递归处理,直到不能上移
}
}
例题 839. 模拟堆
描述
维护一个集合,初始时集合为空,支持如下几种操作:
I x
,插入一个数 x
;
PM
,输出当前集合中的最小值;
DM
,删除当前集合中的最小值(数据保证此时的最小值唯一);
D k
,删除第 k
个插入的数;
C k x
,修改第 k
个插入的数,将其变为 x
;
现在要进行 N 次操作,对于所有第 2 个操作,输出当前集合的最小值。
输入格式
第一行包含整数 N。
接下来 N 行,每行包含一个操作指令,操作指令为 I x,PM,DM,D k 或 C k x 中的一种。
输出格式
对于每个输出指令 PM,输出一个结果,表示当前集合中的最小值。
每个结果占一行。
数据范围
1≤N≤105
−109≤x≤109
数据保证合法。
输入样例:
8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM
输出样例:
输出样例:
-10
6
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10; //堆的大小
int idx,m; //idx表示当前的结点在数组中的下标,m表示插入的第m个数
int h[N],ph[N],hp[N];
void h_swap(int a,int b){
swap(ph[hp[a]],ph[hp[b]]); //交换idx的映射关系
swap(hp[a],hp[b]); //交换m的映射关系
swap(h[a],h[b]); //交换值
}
void down(int u){
int t=u;
int p=u*2; //p表示左儿子节点的下标 p+1表示右儿子
if(p<=idx&&h[p]<h[t]) t=p;
if(p+1<=idx&&h[p+1]<h[t]) t=p+1;
if(t!=u){
h_swap(t,u);
down(t); //递归处理,直到不能下移
}
}
void up(int u){
int t=u>>1;
if(t&&h[u]<h[t]){
h_swap(t,u);
up(t); //递归处理,直到不能上移
}
}
int main(){
cin>>n;
while(n--){
string op;
int k,x;
cin>>op;
if(op=="I"){
cin>>x;
idx++,m++;
ph[m]=idx,hp[idx]=m; //记录映射关系
h[idx]=x; //在末尾插入x
up(idx); //上移x
}
else if(op=="PM"){
cout<<h[1]<<endl;
}
else if(op=="DM"){
h_swap(1,idx); //将最后插入的数覆盖h[1]
idx--;
down(1); //将改变后的h[1]下移
}
else if(op=="D"){
cin>>k;
k=ph[k]; //找到第k个数对应的idx
h_swap(k,idx); //将最后插入的数覆盖h[k]
idx--;
down(k); //将改变后的h[1]下移或上移动,down(k)和up(k)只会执行其一
up(k);
}
else{
cin>>k>>x;
k=ph[k],h[k]=x; //找到第k个数对应的idx,并将h[k]改变为x
down(k); //将改变后的h[k]下移或上移动,down(k)和up(k)只会执行其一
up(k);
}
}
return 0;
}