目录
一、概念
二叉堆:二叉堆是一棵满足“堆性质”的完全二叉树,树上的每个节点都有一个权值。若树中的任意一个结点的权值都小于等于其父节点的权值,则称该二叉树为满足“大根堆性质”的大根堆。若树中的任意一个节点的权值都大于等于其父节点的权值,则称该二叉树为满足“小根堆性质”的小根堆。
注:完全二叉树,其叶子节点都在最后两层,且在最后一层集中于左侧的二叉树
二、图示说明
1.二叉堆
如图1,小根堆,依据层次序列存储方式(逐层从左到右依次为树中的节点编号,把此编号作为节点在数组中存储位置的下标),将每个结点存入一个数组中。
![](https://i-blog.csdnimg.cn/blog_migrate/4d08326458ed43fd092d22f047ead8a7.png)
2.映射版二叉堆
如图2,ph[]中每个k映射到堆中的一个结点,而hp[]反过来从堆的每个结点映射到对应的k
![](https://i-blog.csdnimg.cn/blog_migrate/189ba29617ab5899146f96e89f5f3105.png)
三、操作解释
1.二叉堆
数组模拟,小根堆
a.插入元素
如图3,4,插入一个元素4到堆中。先将该元素放在数组最后面,然后通过和其父节点比对,然后不断向上移。
![](https://i-blog.csdnimg.cn/blog_migrate/a9023b7c0f0da9ac066172bf053de8cf.png)
![](https://i-blog.csdnimg.cn/blog_migrate/1f8d197588e43c478ce28ecee0e621bc.png)
int h[N]; ///存储二叉堆
int si; ///存储二叉堆中最后一个结点的编号
void up(int x) {
///当结点x的父节点存在,且父节点大于节点x时满足条件
if (x / 2 && h[x / 2] > h[x]) {
swap(x, x / 2); ///将x节点和其父节点的权值进行交换
up(x / 2); ///再交换后,让X/2节点继续向上比较
}
}
cin >> x;
h[++si] = x; ///将x插入到数组h的最后一个位置
up(si); ///将插入的x的结点位置传入到up,让x向上比较上移
b.输出最小值
cout << h[1] << endl; ///堆中的第一个结点即为最小值
c.删除最小值
如图5,6,7,8,9所示,删除最小值。先将最小值和堆数组的最后一个元素进行交换,然后删除堆数组的最后一个元素,然后再对交换后的顶部结点进行down操作,和子节点进行比较来向下沉。
![](https://i-blog.csdnimg.cn/blog_migrate/2ba3e23584597dfd20fcc7842b033933.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7462ec8cdd615d09bf70b25d6ec9c75b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e02f94fd6403321afe7f3cf0578b1ead.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d8b83e1c3398e31f51f6b6cb0d0630e5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a105a4c0d782729617c0f214bce8a8d1.png)
void down(int x) {
int t = x;
///将节点x与其两个子节点进行比较,记录x最小值
if (2 * x <= si && h[t] > h[2 * x]) t = 2 * x;
if (2 * x + 1 <= si && h[t] > h[2 * x + 1]) t = 2 * x + 1;
if (t != x) { ///当最小值节点不为x时,将节点x与记录的t节点进行交换
heap_swap(t, x);
down(t); ///让当前节点t继续向下比较
}
}
swap(1, si); ///将节点1和最后一个节点进行权值交换
si--; ///将堆中的最后一个节点减去
down(1); ///将节点1向下比较
d.删除第k个节点
如图10,11,12,13,删除第2个节点。先将堆数组中第2个节点与最后一个节点进行交换,然后删除最后一个节点,最后让交换后的第2个节点进行向下比较。
![](https://i-blog.csdnimg.cn/blog_migrate/c4b787234011f3320cd0287b117c77f6.png)
![](https://i-blog.csdnimg.cn/blog_migrate/39201f92703d8096db39605743f22f03.png)
![](https://i-blog.csdnimg.cn/blog_migrate/26bb46c10926c553a43beae09c869d01.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4d712300c610bab6012bbd7ec8e3e681.png)
void down(int x) {
int t = x;
///将节点x与其两个子节点进行比较,记录x最小值
if (2 * x <= si && h[t] > h[2 * x]) t = 2 * x;
if (2 * x + 1 <= si && h[t] > h[2 * x + 1]) t = 2 * x + 1;
if (t != x) { ///当最小值节点不为x时,将节点x与记录的t节点进行交换
heap_swap(t, x);
down(t); ///让当前节点t继续向下比较
}
}
void up(int x) {
///当结点x的父节点存在,且父节点大于节点x时满足条件
if (x / 2 && h[x / 2] > h[x]) {
heap_swap(x, x / 2); ///将x节点和其父节点的权值进行交换
up(x / 2); ///再交换后,让X/2节点继续向上比较
}
}
cin >>k;
swap(k, si); ///将节点k和最后一个节点进行权值交换
si--; ///将堆中的最后一个节点减去
down(k),up(k); ///将节点k,进行向下比较或向上比较(两个中只会进行一个)
2.映射版二叉堆
a.插入元素
如图14到图17所示,插入元素4。先将元素4加入堆数组的最后一个位置,然后建立k值到结点,结点到k值的映射关系,然后up向上比较元素4的父节点点。比较后发现,元素4比父节点小,故进行交换,先将k值到节点的关系进行交换,然后再将节点到k值的关系进行交换,最后再将两个节点的数值进行交换,然后向上比较发现无法替换,插入结束。
![](https://i-blog.csdnimg.cn/blog_migrate/9c4442bfff4735c2d0bc6ea846595604.png)
![](https://i-blog.csdnimg.cn/blog_migrate/131613920108ee5a65a4dc82213e2873.png)
![](https://i-blog.csdnimg.cn/blog_migrate/57f5350b4cd1e633a95431d8181ce54a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/fdb9a3ded2553624969bc31d9debec56.png)
int ph[N]; ///存储k值到节点的对应关系
int hp[N]; ///存储节点到k值的对应关系
int si; ///存储二叉堆中最后一个结点的编号
int idx; ///存储ph[]数组中k值的最后一个位置
void heap_swap(int a, int b) {
swap(ph[hp[a]], ph[hp[b]]); ///让a,b节点中,k值指向节点的对应关系调换
swap(hp[a], hp[b]); ///再让节点指向k值的对应关系调换
swap(h[a], h[b]); ///最后再将两个节点的权值进行调换
}
void up(int x) {
///当结点x的父节点存在,且父节点大于节点x时满足条件
if (x / 2 && h[x / 2] > h[x]) {
heap_swap(x, x / 2); ///将x节点和其父节点的权值进行交换,并修改对应关系
up(x / 2); ///再交换后,让X/2节点继续向上比较
}
}
h[++si] = x; ///将x插入到堆数组的最后一个位置
///将最后一个结点指向对应的k值,k值指向最后一个节点
hp[si] = ++idx, ph[idx] = si;
up(si); ///将插入的x的结点位置传入到up,让x向上比较上移
b.输出最小值
cout << h[1] << endl; ///堆中的第一个结点即为最小值
c.删除最小值
如图18到图22所示,删除最小值。先将顶部结点与最后一个结点及其映射关系交换,然后再将堆数组的最后一个节点去掉,然后再让顶部结点进行向下比较。
![](https://i-blog.csdnimg.cn/blog_migrate/b030dfb1d9bcd7e07bbab562ab1c4b0f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/533f88daf41cdb801406fa81798f7806.png)
![](https://i-blog.csdnimg.cn/blog_migrate/11c5c3c385968134087a50345c2dc6fd.png)
![](https://i-blog.csdnimg.cn/blog_migrate/48c7ca64f62a2ca10ebb54c5d2da324a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/29222d63e24ec36212be411166b09fde.png)
void down(int x) {
int t = x;
///将节点x与其两个子节点进行比较,记录x最小值
if (2 * x <= si && h[t] > h[2 * x]) t = 2 * x;
if (2 * x + 1 <= si && h[t] > h[2 * x + 1]) t = 2 * x + 1;
if (t != x) { ///当最小值节点不为x时,将节点x与记录的t节点进行交换,并修改其对应关系
heap_swap(t, x);
down(t); ///让当前节点t继续向下比较
}
}
heap_swap(1, si); ///将节点1和最后一个节点进行权值交换,并调整对应关系
si--; ///将堆中的最后一个节点减去
down(1); ///将节点1向下比较
d.删除k对应的节点的值
如图23,24,25,26所示,删除值k9对应的节点。先让k9对应节点与最后一个节点交换权值,及其对应关系,然后删除最后一个节点,最后让交换前k9对应节点进行向下或向上比较。
![](https://i-blog.csdnimg.cn/blog_migrate/34ca5d567567687530c0be110965b1f5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9bfc2260cbf70e35fba45e1d5a657df1.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c36feac14125d5a1772118b10f58a672.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a549d93e1fbe75460e8a82f7dd106d89.png)
cin >> k;
int t = ph[k]; ///记录输入k值对应的结点
heap_swap(t, si); ///交换k值对应节点和最后一个结点及其对应关系
si--; ///减去堆中最后一个节点
down(t),up(t);///让输入k值时,对应的结点进行向上或向下比较
e.修改k值对应节点的值
如图27,28,29所示,将k4对应的节点权值修改为4。先找到k4对应节点权值,然后将其修改为4,最后再让该节点进行向上或向下比较。
![](https://i-blog.csdnimg.cn/blog_migrate/f1f403556dbe71a12527b37746e766ab.png)
![](https://i-blog.csdnimg.cn/blog_migrate/22f72a918b2188a22f51d777c6f86538.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2a60403f1d33d1cec142cd57c1512327.png)
cin >> k >> x;
h[ph[k]] = x; ///让k值所对应的节点权值修改为x
down(ph[k]),up(ph[k]); ///然后让k值所对应的节点,进行向上或向下比较
四、例题实践
1.堆
a.题目描述
题目来源:acwing
输入一个长度为 n 的整数数列,从小到大输出前 m 小的数。
输入格式
第一行包含整数 n 和 m。
第二行包含 n 个整数,表示整数数列。
输出格式
共一行,包含 m 个整数,表示整数数列中前 m 小的数。
数据范围
1≤m≤n≤1e5,
1≤数列中元素≤1e9
输入样例:
5 3
4 5 1 3 2
输出样例:
1 2 3
b.解题思路
思路:先将输入的数存入数组中,然后再从最后一个结点的父节点到第一个结点,每个结点进行向下比较。然后m次输出一个最小值,每一次输出完后,删除最小值。
c.代码实现
#include<iostream>
using namespace std;
const int N=1e5+10;
int h[N],si;
void down(int x){
int t=x;
if(2*x<=si&&h[t]>h[2*x]) t=2*x;
if(2*x+1<=si&&h[t]>h[2*x+1]) t=2*x+1;
if(x!=t){
swap(h[x],h[t]);
down(t);
}
}
int main(){
int n, m;
cin >>n >>m;
for(int i=1;i<=n;i++) cin >>h[i];
si=n;
for(int i=n/2;i>=1;i--) down(i); ///由最后一个节点的父节点开始,向下比较
while(m--){
cout <<h[1] <<' ';
///删除最小值
h[1]=h[si];
si--;
down(1);
}
return 0;
}
2.映射版堆
a.题目描述
维护一个集合,初始时集合为空,支持如下几种操作:
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≤1e5
−1e9≤x≤1e9
数据保证合法。
输入样例:
8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM
输出样例:
-10
6
b.解题思路
思路:因为在题目中,要求输出能最小值,同时还要能修改k值对应节点的权值,所以采用映射版堆来维护该集合
c.代码实现
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int h[N]; ///存储二叉堆
int ph[N]; ///存储k值到节点的对应关系
int hp[N]; ///存储节点到k值的对应关系
int si; ///存储二叉堆中最后一个结点的编号
int idx; ///存储ph[]数组中k值的最后一个位置
char op[2];
int n;
void heap_swap(int a, int b) {
swap(ph[hp[a]], ph[hp[b]]); ///让a,b节点中,k值指向节点的对应关系调换
swap(hp[a], hp[b]); ///再让节点指向k值的对应关系调换
swap(h[a], h[b]); ///最后再将两个节点的权值进行调换
}
void down(int x) {
int t = x;
///将节点x与其两个子节点进行比较,记录x最小值
if (2 * x <= si && h[t] > h[2 * x]) t = 2 * x;
if (2 * x + 1 <= si && h[t] > h[2 * x + 1]) t = 2 * x + 1;
if (t != x) { ///当最小值节点不为x时,将节点x与记录的t节点进行交换,并修改其对应关系
heap_swap(t, x);
down(t); ///让当前节点t继续向下比较
}
}
void up(int x) {
///当结点x的父节点存在,且父节点大于节点x时满足条件
if (x / 2 && h[x / 2] > h[x]) {
heap_swap(x, x / 2); ///将x节点和其父节点的权值进行交换,并修改对应关系
up(x / 2); ///再交换后,让X/2节点继续向上比较
}
}
int main() {
cin >> n;
while (n--) {
cin >> op;
int k;
long long x;
if (op[0] == 'I') {
cin >> x;
h[++si] = x; ///将x插入到堆数组的最后一个位置
///将最后一个结点指向对应的k值,k值指向最后一个节点
hp[si] = ++idx, ph[idx] = si;
up(si); ///将插入的x的结点位置传入到up,让x向上比较上移
}
else if (op[0] == 'P') {
cout << h[1] << endl; ///堆中的第一个结点即为最小值
}
else if (op[0] == 'D' && op[1] == 'M') {
heap_swap(1, si); ///将节点1和最后一个节点进行权值交换,并调整对应关系
si--; ///将堆中的最后一个节点减去
down(1); ///将节点1向下比较
}
else if (op[0] == 'D' && op[1] != 'M') {
cin >> k;
int t = ph[k]; ///记录输入k值对应的结点
heap_swap(t, si); ///交换k值对应节点和最后一个结点及其对应关系
si--; ///减去堆中最后一个节点
down(t),up(t);///让输入k值时,对应的结点进行向上或向下比较
}
else {
cin >> k >> x;
h[ph[k]] = x; ///让k值所对应的节点权值修改为x
down(ph[k]),up(ph[k]); ///然后让k值所对应的节点,进行向上或向下比较
}
}
return 0;
}