堆分为小根堆和大根堆,小根堆的父节点都要比子节点的值小,大根堆相反。
堆的存储使用一个一维数组来存储的,数组的下标我们是从1开始的,根节点下标为x的左孩子的下标为2x,右孩子的下标为2x+1.
如何手写一个堆?
1.插入一个数
2.求集合当中的最小值
3.删除最小值
4.删除任意一个元素
5.修改任意一个元素
例一:堆排序
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n, m;
int h[N], cnt;//数组h表示堆,cnt表示堆里面存储的元素个数
void down(int u)
{
int t = u;//t存储三个结点中存在的最小的结点的下标,初始化为当前结点u
if(u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;// 左子节点存在并且小于当前结点,更新t的下标
if(u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;//右子节点存在并且小于当前结点,更新t的下标
if(u != t)//如果t==u意味着不用变动,u就是三个结点中拥有最小值的结点下标,否则交换数值
{
swap(h[u], h[t]);
//交换数值后,t这个结点存储原本u的值,u存储存储t的值(三个数中的最小值)
//u不用调整了,但t情况不明,可能需要调整。直到它比左右子节点都小
down(t);
}
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) scanf("%d", &h[i]);
cnt = n;
for(int i = n / 2; i; --i) down(i);//把堆初始化成小根堆,从二叉树的倒数第二行开始,把数字大的下沉
while(m--)
{
printf("%d ", h[1]);
h[1] = h[cnt--];
down(1);
}
}
例题二:模拟堆
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
//数组h表示堆,数组ph表示存放第k个插入点的下标
//数组hp表示存放堆中点的插入次序,cnt表示记录的是堆当前的个数多少
//ph数组主要用于帮助从idx映射到下标k
int h[N], ph[N], hp[N], cnt;
//hp[u]=k 则ph[k]=u 是映射关系
//之所以要进行这样的操作是因为 经过一系列操作 堆中的元素并不会保持原有的插入顺序
//从而我们需要对应到原先第K个堆中元素
//如果理解这个原理 那么就能明白其实三步交换的顺序是可以互换
//h,hp,ph之间两两存在映射关系 所以交换顺序的不同对结果并不会产生影响
void heap_swap(int a, int b)
{
swap(ph[hp[a]], ph[hp[b]]);
swap(hp[a], hp[b]);
swap(h[a], h[b]);
}
void down(int u)
{
int t = u;
if(u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
if(u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if(u != t)
{
heap_swap(u, t);
down(t);
}
}
void up(int u)
{
while(u / 2 && h[u] < h[u / 2])
{
heap_swap(u, u / 2);
u >>= 1;
}
}
int main()
{
int n, m = 0;//m用来记录插入的数的个数
//注意m的意义与cnt是不同的 cnt是记录堆中当前数据的多少
//对应上文 m即是hp中应该存的值
scanf("%d", &n);
while(n--)
{
char op[5];
int k, x;
scanf("%s", op);
if(op == "I")
{
scanf("%d", &x);
cnt++;
m++;
ph[m] = cnt, hp[cnt] = m;
up(cnt);
}
else if(op == "PM") printf("%d", h[1]);
else if(op == "DM")
{
heap_swap(1, cnt);
cnt--;
down(1);
}
else if(op == "D")
{
scanf("%d", &k);
int u = ph[k];//这里一定要用u=ph[k]保存第k个插入点的下标
heap_swap(u, cnt);//因为在此处heap_swap操作后ph[k]的值已经发生
cnt--;
up(u);
down(u);
}
else
{
scanf("%d%d", &k, &x);
k = ph[k];
h[k] = x;
up(k);
down(k);
}
}
return 0;
}