一、定义
维护一个数据集合,堆是一个完全二叉树。
那么什么是二叉树呢?
如图:
二、关于小根堆实现
性质:每个根节点都小于等于左右两边,所以树根为最小值。
2.1、堆存储(用一维数组来存)
记住规则:x(根)的左儿子 = x * 2;
x(根)的右儿子 = x * 2 + 1
样例如图:
2.2、操作
2.2.1、操作1、down(x) :节点下移
如果把一个值变大了,就让他往下移动。
样例:
2.2.2、操作2、 up(x):节点向上移
如果把某一个值变小了,就让他向上移动
样例:
2.3、如何手写一个堆?
注意:下标要从1开始,heap[]堆,size:堆长度
如图:
这里后面会有对应的例题,详细解释4、5操作
三、例题:
3.1、堆排序 :此题来源于acwing
图解:
上述两个图来自于B站董晓算法的视频
AC代码:
#include<iostream>
#include<algorithm>using namespace std;const int N = 1e5+10;
int h[N],sz;
int n,m;void down(int u)
{int t = u;if(u*2 <= sz && h[u*2] < h[t]) t = u * 2;if(u*2+1 <= sz && h[u*2+1] < h[t]) t = u * 2 + 1;if(t != u){swap(h[t],h[u]);down(t);}
}int main()
{scanf("%d %d", &n, &m);for(int i=1;i<=n;i++) scanf("%d ",&h[i]);sz = n;//构建堆,从n/2序号开始创建,把最小值挪到树根,相当于忽略最后一层for(int i=n/2;i>=1;i--){down(i);}while (m -- ){printf("%d ",h[1]);h[1] = h[sz];sz--;down(1);}return 0;
}#include<iostream>
#include<algorithm>using namespace std;const int N = 1e5+10;
int h[N],sz;
int n,m;
//下沉
void down(int u)
{int t = u;//根据小根堆性质,保证父亲节点是小于左右两个儿子的if(u*2 <= sz && h[u*2] < h[t]) t = u * 2;if(u*2+1 <= sz && h[u*2+1] < h[t]) t = u * 2 + 1;if(t != u){swap(h[t],h[u]);//如果不同,需要交换两个节点down(t);//继续下沉}
}int main()
{scanf("%d %d", &n, &m);for(int i=1;i<=n;i++) scanf("%d ",&h[i]);sz = n;//记得传一下长度给sz。//构建堆,从n/2序号开始创建,把最小值挪到树根,相当于忽略最后一层for(int i=n/2;i>=1;i--){//这里非常巧妙,从后面开始去下沉,等到树根的时候,会第一个开始down,//会排好,不用担心乱序down(i);}while (m -- ){printf("%d ",h[1]);//取出最小值h[1] = h[sz]; //根据规则操作后续步骤sz--;down(1);}return 0;
}
3.2、模拟堆:此题同样来源于acwing
思路:
由于,此题需要记录一下第k个插入的数,所以需要用两个映射的数组去维护一下第k个插入的数,和当前堆中元素的下标,此处附上一位acwing评论区的一位大佬的讲解,我觉得讲的非常好,可以帮助此题理解两个数组的含义,建议先看代码,再看这个。代码中有详细注释,如果有错误欢迎指出~。
AC代码:
#include<iostream>
#include<algorithm>
#include<cstring>using namespace std;const int N = 1e5+10;
//h[]用来存堆,ph[]用来存第k个数对应的堆里的下标
//hp[]用来存堆下标下是第几个存入的数
int h[N],ph[N],hp[N],sz;
int n,m;void heap_swap(int a,int b)
{swap(h[a],h[b]);//交换堆中的两个数值swap(hp[a],hp[b]);//在堆中对应的下标下是第几个存入的数,交换一下swap(ph[hp[a]],ph[hp[b]]);//交换一下堆中的下标
}
//下沉
void down(int u)
{int t = u;if(u*2 <= sz && h[u*2] < h[t]) t = u * 2;if(u*2+1 <= sz && h[u*2+1] < h[t]) t = u * 2 + 1;if(t != u){heap_swap(t,u); //由于后面需要找到第k的数,所以不能用普通的交换,需要用我们手写的交换函数down(t);//下沉,直到不满足位置}
}
//上浮
void up(int u)
{//与根部比较,如果父亲大于儿子就需要上浮while(u/2 && h[u/2] > h[u]){heap_swap(u/2, u);u = u/2;}
}int main()
{scanf("%d", &n);while (n -- ){char op[10];//根据题目要求输入字符串scanf("%s", op);if(!strcmp(op,"I")){int x;scanf("%d", &x);m++;//第几个插入的sz++;//当前下标//ph表示第k插入的数在堆中的下标是多少//hp表示该数堆中下标对应第几个插入的数ph[m] = sz,hp[sz] = m;h[sz] = x;//堆中存入xup(sz);//上浮}else if(!strcmp(op,"PM")) //找到最小的数,就是树根{printf("%d\n",h[1]);}else if(!strcmp(op,"DM")) //注意交换后sz--;{heap_swap(1,sz);sz--;down(1);}else if(!strcmp(op,"D")) //删除第k个数{int k;scanf("%d", &k);k = ph[k]; //找到第k个数下的堆中的元素下标heap_swap(k,sz);sz--;down(k),up(k);//down和up只会执行一个操作,因为要么大,要么小}else{int k,x;scanf("%d %d", &k,&x);k = ph[k];h[k] = x;down(k),up(k);//同理}}return 0;
}
看会以上,大家可以去做一下这个题:
P3378 【模板】堆 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题解在这里:
P3378 【模板】堆-CSDN博客
感谢观看~