-
堆的特征
1)堆中某个节点的值总是不大于或不小于其父节点的值;将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
2)堆总是一棵完全二叉树。 -
堆结构中用到二叉树的一些性质
1)设任意一个节点为x则该节点的左儿子是2x,右儿子是2x+1
2)叶子节点占全部节点的二分之一 -
模板
// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1
// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
int h[N], ph[N], hp[N], size;
// 交换两个点,及其映射关系
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 <= size && h[u * 2] < h[t]) t = u * 2; //此处第二个比较一定要和t比才能让最小值与u交换
if (u * 2 + 1 <= size && 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;
}
}
// O(n)建堆
for (int i = n / 2; i; i -- ) down(i);
-
堆的基本操作
1)down(x)
2) up(x) -
由两个基本操作组合的其他操作
1)插入一个数:heap[++size] = x; up[size];
2)求集合当中的最小值:heap[1];
3)删除最小值:heap[1] = heap[size]; size–; down(1);
4)删除任意一个元素
5)修改任意一个元素 -
例题:堆排序
输入一个长度为n的整数数列,从小到大输出前m小的数。
输入格式
第一行包含整数n和m。
第二行包含n个整数,表示整数数列。
输出格式
共一行,包含m个整数,表示整数数列中前m小的数。
数据范围
1≤m≤n≤105,
1≤数列中元素≤109
输入样例:
5 3
4 5 1 3 2
输出样例:
1 2 3
#include<iostream>
#include<algorthm>
using namespace std;
const int N = 100010;
int n, m, size;
int h[N];
void down(int x)
{
int t = x;
if(2*x <= size && h[2*x] < h[t]) t = 2*x; //若左儿子小于x,则左儿子和x进行交换
if(2*x+1 <= size && h[2*x+1] < h[t]) t = 2*x+1; //若右儿子小于x,则右儿子和x进行交换
if(t != x)
{
swap(h[t], h[x]);
down(t); //递归,使x下降到不能再下降的层数
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) scanf("%d", &h[i]);
size = n; //记录h中的节点个数
for(int i = n/2; i; i--) down(i); //建一个堆,从堆的第二层开始down,直到最顶层
//进行m次输出
while(m--)
{
printf("%d ", h[1]);
//删除最小值
h[1] = h[size];
size--;
down(1);
}
return 0;
}
- 例题:模拟堆
维护一个集合,初始时集合为空,支持如下几种操作:
1、“I x”,插入一个数x;
2、“PM”,输出当前集合中的最小值;
3、“DM”,删除当前集合中的最小值(数据保证此时的最小值唯一);
4、“D k”,删除第k个插入的数;
5、“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<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int N = 100010;
int h[N], ph[N], hp[N], size;
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 <= 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(u != t)
{
heap_swap(t, u);
down(t);
}
}
void up(int u)
{
while(u / 2 && h[u/2] > h[u])
{
heap_swap(u/2, u);
u /= 2;
}
}
int main()
{
int n, m = 0;
scanf("%d", &n);
while(n--)
{
char op[10];
int k, x;
scanf("%s", op);
if(!strcmp(op, "I"))
{
scanf("%d", &x);
size++, m++; //m存储当前插入的是第几个数,size存储当前堆中有多少个数
ph[m] = size, hp[size] = m;
h[size] = x; //注意下标为size是堆尾的序号
up(size);
}
else if(!strcmp(op, "PM")) printf("%d\n", h[1]);
else if(!strcmp(op, "DM"))
{
heap_swap(1, size);
size--;
down(1);
}
else if(!strcmp(op, "D"))
{
scanf("%d", &k);
k = ph[k];
heap_swap(k, size);
size--;
down(k), up(k); //此处两个语句只会执行其中一个
}
else
{
scanf("%d%d", &k, &x);
k = ph[k];
h[k] = x;
down(k), up(k);
}
}
return 0;
}
[^1]此文章中的代码和模板均来自www.acwing.com