基本定义

  • 堆是一个完全二叉树(除最后一层节点外,所有节点都是非空的,最后一层是从左到右依次排布的)

  • 堆(小根堆),每个点都小于等于两个儿子的最小值

  • 堆的数组实现,用一维数组即可存储

​ 根节点存在1号位,x点的左儿子:2x

​ x点的右儿子:2x+1

基本操作

==这五个操作通过down(x)和up(x)都可以实现==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//down操作
void down(int u){
int t = u;//用t来表示u,u的两个儿子这三个点中的最小值
if(u * 2 <= siz && h[u * 2] <h[t]) t = u * 2;//如果左儿子比u小,t就赋值左儿子
if(u * 2 + 1 <= siz && h[u * 2 + 1] < h[t]) t = u * 2 + 1;//同上
if(u != t){//如果u不等于t,那就说明u不是三个点中的最小值,
swap(h[u], h[t]);
down(t);
}
}
//up操作
void up(int u){
while(u / 2 && h[u / 2] > h[u]){
swap(h[u / 2], h[u]);
u /= 2;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
heap代表堆数组,size代表堆的大小
* 插入一个数
将x插到heap的最后一位,再up
heap[++size] = x; up(size);
* 求集合当中的最小值
小根堆的第一个点就是最小值
heap[1];
* 删除最小值
因为一维数组删掉尾结点很容易(只需要size--),所以可以将尾结点覆盖到头结点(也就是最小值),然后down
heap[1] = heap[size];size--;down(1);
* 删除任意一个元素
同上。因为不知道heap[k]变大还是变小,也不需要判断,直接down和up就行(只会执行一个)
heap[k] = heap[size];size--;down(k);up(k);
* 修改任意一个元素
同上
heap[k] = x;down(k);up(k);

例题1.堆排序

1
2
3
4
5
6
7
8
9
10
11
12
13
输入一个长度为 n 的整数数列,从小到大输出前 m 小的数。

输入格式
第一行包含整数 n 和 m。

第二行包含 n 个整数,表示整数数列。

输出格式
共一行,包含 m 个整数,表示整数数列中前 m 小的数。

数据范围
1≤m≤n≤105,
1≤数列中元素≤109
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//只需要实现输出堆顶数和删除堆顶数即可完成此题
//所以只需要实现down操作即可
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;

int n, m;
int h[N], siz;

void down(int u){
int t = u;//用t来表示u,u的两个儿子这三个点中的最小值的编号
if(u * 2 <= siz && h[u * 2] <h[t]) t = u * 2;//如果左儿子比u小,t就赋值左儿子
if(u * 2 + 1 <= siz && h[u * 2 + 1] < h[t]) t = u * 2 + 1;//同上
if(u != t){//如果u不等于t,那就说明u不是三个点中的最小值,
swap(h[u], h[t]);
down(t);
}
}

int main(){
ios::sync_with_stdio(false);
cin.tie(0);

cin>>n>>m;
for(int i = 1; i <= n; i ++) cin>>h[i];
siz = n;

//如何将数组建成堆
for(int i = n / 2; i; i--) down(i);

while(m--){
cout<<h[1]<<" ";
h[1] = h[siz];
siz --;
down(1);
}
return 0;
}

例题2.模拟堆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
维护一个集合,初始时集合为空,支持如下几种操作:

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≤105
−109≤x≤109
数据保证合法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <iostream>
#include <algorithm>
#include <string.h>

using namespace std;

const int N = 1e5 + 10;

int ph[N], hp[N];
//想找到第k个插入的数,需要两个维护数组
//ph存的是第k个点在堆中的下标
//hp存的是堆中的点对应在ph数组中的下标
int h[N], siz;
int n, m;//m用来记录当前插入的是第几个数
//交换堆中数值的同时,维护数组也要跟着交换
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 (2 * u <= siz && h[2 * u] < h[t])
t = 2 * u;

if (2 * u + 1 <= siz && h[2 * u + 1] < h[t])
t = 2 * u + 1;

if (t != u) {
heap_swap(u, t);
down(t);
}
}

void up(int u ) {
while (u / 2 && h[u / 2] > h[u]) {
heap_swap(u / 2, u);
u /= 2;
}
}

int main() {
ios::sync_with_stdio(false);
cin.tie(0);

cin >> n;

while (n--) {
char op[10];
int k, x;
cin >> op;

if (!strcmp(op, "I")) {
cin >> x;
siz ++;
m++;
ph[m] = siz, hp[siz] = m;
h[siz] = x;
up(siz);
} else if (!strcmp(op, "PM"))
cout << h[1] << endl;
else if (!strcmp(op, "DM")) {
heap_swap(1, siz);
siz--;
down(1);
} else if (!strcmp(op, "D")) {
cin >> k;
k = ph[k];//找到k在堆中的位置
heap_swap(k, siz);
siz--;
down(k), up(k);
} else {
cin >> k >> x;
k = ph[k];
h[k] = x;
down(k), up(k);
}
}

return 0;
}