堆
存储方式
用一维数组来存储一棵树。 从数组下标1开始存储,对于下标为
x
x
x的元素,其左孩子的下标为
2
x
2x
2x,右孩子的下标为
2
x
+
1
2 x+1
2x+1
两个基本操作
- down(x)
从第x个结点处向下调整二叉树,使得该二叉树再次满足堆的定义。例如在小根堆中若堆顶元素变大了,则需要逐步将该元素向下移动,直至到达一个合适的位置。
void down(int r)
{
int min = r;
if (r * 2 <= size && h[r * 2] < h[min]) min = r * 2;
if (r * 2 + 1 <= size && h[r * 2 + 1] < h[min]) min = r * 2 + 1;
if (min != r)
{
swap(h[min], h[r]);
down(min);
}
}
- up(x)
从第x个结点处向上调整二叉树,使得该二叉树再次满足堆的定义
void up(int r)
{
while (r / 2 > 0 && h[r / 2] > h[r])
{
swap(h[r / 2], h[r]);
r /= 2;
}
}
可以实现的功能
操作 | 代码 |
---|
插入一个数 | heap[++ size] = x; up(size); | 求堆顶元素 | heap[1] | 删除堆顶元素 | heap[1] = heap[size]; size --; down(1); | 删除任意一个元素 | heap[k] = heap[size]; size --; down(k); up(k); | 修改任意一个元素 | heap[k] = x; down(k); up(k); |
建立堆
down(x)、up(x)的时间复杂度均为
O
(
l
o
g
n
)
O(logn)
O(logn),以建立含n个元素的小根堆为例,若每插入一个元素就down一次,则建堆的时空复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。 此处先建立数组,再将数组转化为堆,从二叉树的倒数第2层的位置开始down,使建堆过程的时间复杂度优化为
O
(
n
)
O(n)
O(n)
for (int i = 1; i <= n; i ++) cin >> h[i];
size = n;
for (int i = n / 2; i >= 1; i --) down(i);
例题1:堆排序
#include <iostream>
using namespace std;
const int N = 100010;
int h[N], siz;
void down(int r)
{
int min = r;
if (r * 2 <= siz && h[r * 2] < h[min]) min = r * 2;
if (r * 2 + 1 <= siz && h[r * 2 + 1] < h[min]) min = r * 2 + 1;
if (min != r)
{
swap(h[min], h[r]);
down(min);
}
}
int main()
{
int m, n;
cin >> n >> m;
for (int i = 1; i <= n; i ++) cin >> h[i];
siz = n;
for (int i = n / 2; i >= 1; i --) down(i);
while (m --)
{
cout << h[1] << ' ';
h[1] = h[siz];
siz --;
down(1);
}
return 0;
}
带映射的堆
有时我们需要记录第k个插入的数是什么,此时便需要再维护两个一维数组ph和hp。 ph[k] = i 表示第k个插入的元素在堆中的下标为i;hp[i] = k 表示堆中下标为i的元素是第k个插入的。 ph中存的是下标,hp中存的是插入顺序,这两个数组互相映射。
此时若想要交换堆中的两个元素,我们不仅要交换这两个元素的值,还要修改对应的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]);
}
函数down、up中的swap也要改为heap_swap
插入元素
void insert(int x)
{
h[++ siz] = x;
cnt ++;
ph[cnt] = siz, hp[siz] = cnt;
up(siz);
}
删除堆顶元素
heap_swap(1, siz);
siz --;
down(1);
删除第k个插入的元素
void del(int k)
{
int t = ph[k];
heap_swap(t, siz);
siz --;
down(t), up(t);
}
修改第k个插入的元素
void modify(int k, int x)
{
int t = ph[k];
h[t] = x;
down(t), up(t);
}
例题2,模拟堆
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010;
int h[N], hp[N], ph[N], siz;
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 r)
{
int min = r;
if (r * 2 <= siz && h[r * 2] < h[min]) min = r * 2;
if (r * 2 + 1 <= siz && h[r * 2 + 1] < h[min]) min = r * 2 + 1;
if (min != r)
{
heap_swap(min, r);
down(min);
}
}
void up(int r)
{
while (r / 2 > 0 && h[r / 2] > h[r])
{
heap_swap(r / 2, r);
r /= 2;
}
}
int main()
{
int n, cnt = 0;
cin >> n;
while (n --)
{
char op[5];
scanf("%s", op);
if (strcmp(op, "I") == 0)
{
int x;
cin >> x;
h[++ siz] = x;
cnt ++;
ph[cnt] = siz, hp[siz] = cnt;
up(siz);
}
else if (strcmp(op, "PM") == 0) cout << h[1] << endl;
else if (strcmp(op, "DM") == 0)
{
heap_swap(1, siz);
siz --;
down(1);
}
else if (strcmp(op, "D") == 0)
{
int k;
cin >> k;
int t = ph[k];
heap_swap(t, siz);
siz --;
down(t), up(t);
}
else
{
int k, x;
cin >> k >> x;
int t = ph[k];
h[t] = x;
down(t), up(t);
}
}
return 0;
}
|