文章目录
堆
1.概述
堆即完全二叉树,用于维护一个数据集合。可实现一系列操作。
2.举例:小根堆
-
每个点小于等于左右儿子
-
根节点是最小值
存储
用数组存储,第一个位置即为根节点(注意从1开始存)
x
的左儿子:2x
x
的右儿子:2x+1
基本操作
down(x)
:值变大后需要下移
up(x)
:值变小后需要上移
3.基本要求
① 插入一个数
heap[++ size] = x;
up(size);
② 求最小值
heap[1]
③ 删除最小值
删除最后一个数方便,将根节点数换为最后一个数,后删除最后一个数,再将当前根节点进行down
操作
heap[1] = heap[size];
size--;
down(1);
④ 删除任意元素
思想同上
heap[k] = heap[size];
size--;
up(k);down(k);
⑤ 修改任意元素
heap[k] = x;
up(k);down(k);
4.基本操作
① down(x)
void down(int u)
{
int t = u;
if(u*2 <= size && h[u*2] < h[t]) t = u*2;
if(u*2 + 1 <= size && h[u*2 + 1] < h[t]) t = u*2 + 1;
if(t != u)
{
swap(h[u],h[t]);
down(t);
}
}
② up(x)
void up(int u)
{
while(u/2 && h[u/2] > h[u])
{
swap(h[u],h[u/2]);
u /= 2;
}
}
5.例题:堆排序
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int h[N], cnt;
void down(int u)
{
int t = u;
// t存最小值
if(2*u <= cnt && h[2*u] < h[t]) t = 2*u;
if(2*u + 1 <= cnt && h[2*u + 1] < h[t]) t = 2*u + 1;
if(u != t)
{
swap(h[u], h[t]);
down(t);
}
}
int main()
{
int n,m;
cin >> n >> m;
for(int i = 1; i <= n; i++) scanf("%d",&h[i]);
cnt = n;
/* 从倒数第二层(n/2)往上调整
1. 下标尽可能大,因为要从下往上遍历
2. 有左右儿子
满足上面两个条件就是倒数第二层的n/2个点(比n/2大的没有左右儿子,比n/2小的下标不最大)
*/
// for(int i = 1; i <= n; i++) up(i);
for(int i = n / 2; i; i -- ) down(i);
while(m --)
{
printf("%d ",h[1]); //输出最小值
h[1] = h[cnt]; //输出最小值后需要更新(类似删除最小值)
cnt--;
down(1);
}
return 0;
}
6.拓展 - 模拟堆
该题需要记录第k个插入的数,新增两个数组。
(两个数组是对应的)
ph[k]
:存下标,第k个插入的数的下标
hp[k]
:存插入,k下标是第几个插入
例如:ph[i] = j
则 hp[j] = i
(第i个插入的数下标为j,j下标为第i个插入)
介于新增数组,在交换两个数时同时也需要改变这两个数组的指向
void swap_heap(int a, int b)
{
swap(h[a], h[b]);
swap(ph[hp[a]], ph[hp[b]]);
swap(hp[a], hp[b]);
}
具体代码
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
const int N = 100010;
int h[N],ph[N],hp[N],cnt;
void swap_heap(int a, int b)
{
swap(h[a],h[b]);
swap(ph[hp[a]],ph[hp[b]]);
swap(hp[a],hp[b]);
}
void down(int u)
{
int t = u;
if(2*u <= cnt && h[2*u] < h[t]) t = 2*u;
if(2*u + 1 <= cnt && h[2*u + 1] < h[t]) t = 2*u + 1;
if(t != u)
{
swap_heap(u,t);
down(t);
}
}
void up(int u)
{
while(u/2 && h[u/2] > h[u])
{
swap_heap(u/2, u);
u /= 2;
}
}
int main()
{
int n,m=0; //用m记录第几个插入的
scanf("%d",&n);
while(n--)
{
int x, k;
char op[10];
scanf("%s",op);
if(!strcmp(op,"I"))
{
scanf("%d", &x);
cnt++;
m++;
h[cnt] = x;
ph[m] = cnt;
hp[cnt] = m;
up(cnt);
}
else if(!strcmp(op,"PM")) printf("%d\n",h[1]);
else if(!strcmp(op,"DM"))
{
swap_heap(1,cnt);
cnt--;
down(1);
}
else if(!strcmp(op,"D"))
{
scanf("%d", &k);
k = ph[k]; //k改为了第k个插入的下标下标
swap_heap(k,cnt); //删除操作
cnt --;
down(k);
up(k);
}
else
{
scanf("%d%d", &k,&x);
k = ph[k];
h[k] = x;
down(k);
up(k);
}
}
return 0;
}