数据结构 - 堆

堆,又叫优先队列。堆是一颗完全二叉树结构(除了最后一层节点之外,上面所有节点都是为满的,最后一层,从左到右排列)。

手写堆的基本要求:

  • 插入一个数
  • 求集合中的最小值
  • 删除最小值
  • 删除任意一个元素
  • 修改任意一个元素

小根堆:

  • 根节点小于左右两边的节点。

大根堆:

  • 根节点大于左右两边的节点。

堆的两个基本操作:

down:

把一个节点往下移动,使满足根堆的性质

  • 找到最小值与其交换
  • 交换到不等交换

up

把一个节点往上移动,使满足根堆的性质

  • 如果根节点比当前节点大,就交换
  • 交换到不能交换或者到顶根节点

堆节点的组合操作

插入操作:

  • heap[++size] = x, up(x)

删除最小值操作:

  • heap[1] = heap[size], size–; down(1);

删除任意一个元素:

  • heap[k] = heap[size]; size–; up[k];

修改任意一个元素:

  • heap[k] = x; down(k); up(k);

注意: 下标从1开始比较方便。

题目:堆排序

来源: https://www.acwing.com/problem/content/840/

输入一个长度为n的整数数列,从小到大输出前m小的数。

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

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

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

数据范围

$1≤m≤n≤10^5,$
$1≤数列中元素≤10^9$

输入样例:

1
2
5 3
4 5 1 3 2

输出样例:

1
1 2 3
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
#include <iostream>

using namespace std;
const int N = 100010;

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

void down(int x) {
int t = x;
if (2 * x <= cnt && h[2 * x] < h[t]) t = 2 * x;
if (2 * x + 1 <= cnt && h[2 * x + 1] < h[t]) t = 2 * x + 1;
if (t != x) {
swap(h[t], h[x]);
down(t);
}
}


int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) scanf("%d", &h[i]);

cnt = n;
for(int i = cnt / 2; i; i--) down(i);

for(int i = 0; i < m; i++) {
printf("%d ", h[1]);
h[1] = h[cnt--];
down(1);
}
return 0;
}

题目: 模拟堆

来源: https://www.acwing.com/problem/content/841/

维护一个集合,初始时集合为空,支持如下几种操作:

“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≤10^5$
$−10^9≤x≤10^9$
数据保证合法。

输入样例:

1
2
3
4
5
6
7
8
9
8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM

输出样例:

1
2
-10
6
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
84
85
86
87
88
89
90
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;

const int N = 100010;
// h 表示堆数据 cnt 表示堆的大小
// ph 表示插入的第k个数在h中的角标
// hp 表示h中的第k个数是插入的第n个数
int h[N], ph[N], hp[N], cnt;

void heap_swap(int x, int y)
{
// x, y 为角标
// 首先交换插入的第k个数之间的角标
// 再堆中k个数对应的角标
// 最后交换堆中的值
swap(ph[hp[x]], ph[hp[y]]);
swap(hp[x], hp[y]);
swap(h[x], h[y]);
}

void down(int x)
{
int t = x;
if ( 2 * x <= cnt && h[2 * x] < h[t]) t = 2 * x;
if ( 2 * x + 1 <= cnt && h[2 * x + 1] < h[t]) t = 2 * x + 1;
if (t != x)
{
heap_swap(t, x);
down(t);
}
}

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


int main()
{
int n, m = 0;
scanf("%d", &n);
while (n -- )
{
char op[5];
// k 表示第k个数,x 表示输入的数据
int k, x;
scanf("%s", op);
// strcmp 是一个字符匹比较函数
// 如果相等返回就是0, 不等返回一个正数
if (!strcmp(op, "I"))
{
scanf("%d", &x);
cnt ++ ;
m ++ ;
ph[m] = cnt, hp[cnt] = m;
h[cnt] = x;
up(cnt);
}
else if (!strcmp(op, "PM")) printf("%d\n", h[1]);
else if (!strcmp(op, "DM"))
{
heap_swap(1, cnt);
cnt -- ;
down(1);
}
else if (!strcmp(op, "D"))
{
scanf("%d", &k);
k = ph[k];
heap_swap(k, cnt);
cnt -- ;
up(k);
down(k);
}
else
{
scanf("%d%d", &k, &x);
k = ph[k];
h[k] = x;
up(k);
down(k);
}
}
}