一、堆的概念
1、堆的性质
- 是一颗完全二叉树
- 每个节点的值都大于或等于子节点的值,为最大堆;反之为最小堆
2、堆的存储
一般用数组来表示堆,下标从0开始。则下标为 i 的节点的父节点下标为(i-1)/2
,其左右子节点分别为(2i + 1)
、(2i + 2)
。
下标从1开始 左右节点2i 、 2i+1
二、堆排序
1、基本思想
利用大顶堆(小顶堆)堆顶记录的是最大(小)关键字这一特性,每次从无序数组中选出最大(最小)值。
1、将待排序序列造成一个最大堆,此时根节点为最大值
2、依次将根节点与待排序序列最后一个元素交换
3、维护从根节点到该元素的前一个节点为最大堆
2、基本操作
down:把数往下沉,从而再次变成堆的操作,要考虑和两个左右子节点比较。
up:底下的数往上走,再次变成堆的操作,只要和父节点比较就可以。
插入一个数:heap[++size], up(size)
删除最小值:把最后一个元素覆盖到堆顶,heap[1] = heap[size], size--, down(1)
删除任意一个元素:heap[k]=heap[size];size--;down(k);up(k)
;dwon和up只会执行一个
修改任意元素:heap[k]=x;down();up()
时间复杂度:nlog n
堆排序:每次取出堆顶后,把最后一个数放到堆顶,进行下沉操作,直到堆最后只剩一个元素为止。
[HTML_REMOVED]模板[HTML_REMOVED]
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int h[N], mySize;
int n, m;
void down(int u)
{
int t = u;
if (2 * u <= mySize && h[t] > h[2 * u])
t = 2 * u; // 先比较左儿子
if (2 * u + 1 <= mySize && h[t] > h[2 * u + 1]) // 再比较右儿子,如果上面更新了则h[t]是左儿子的值,最终h[t]=3个点中最小的值
t = 2 * u + 1;
if (u != t) // 根节点不是最小值,交换,把根节点下沉down
{
swap(h[u], h[t]);
down(t);
}
}
int main()
{
cin >> n >> m;
mySize = n;
for (int i = 1; i <= n; i++)
scanf("%d", &h[i]);
for (int i = n / 2; i; i--)
down(i); // 建立初始堆
while (m--)
{
cout << h[1] << " ";
h[1] = h[mySize--];
down(1); // 最后一个数放到堆顶,要下沉操作
}
return 0;
}
三、模拟堆
[HTML_REMOVED]模拟堆[HTML_REMOVED]
维护一个集合,初始时集合为空,支持如下几种操作:
I x
,插入一个数 x;PM
,输出当前集合中的最小值;DM
,删除当前集合中的最小值(数据保证此时的最小值唯一);D k
,删除第 k 个插入的数;C k x
,修改第 k个插入的数,将其变为 x;
现在要进行 N次操作,对于所有第 22 个操作,输出当前集合的最小值。
定义两个数组:分别是 hp 和 ph
hp-->
堆数组中下标 -> 第k个插入
heap pointer
ph-->
第k个插入 -> 堆数组中下标
pointer heap
这两个函数是互为反函数的。
当在堆中交换两个元素时,那么第k个插入的数的下标要变化、下标对应的数是第几个也要变化。swap操作传入需要交换的堆数组的下标
void swap_heap(int a, int b) {
swap(ph(hp[a]), ph[hp[b]]); // 第k个插入的数的下标变化
swap(hp[a],hp[b]); //
swap(h[a],h[b]); // 交换数组响应下标对应的数值
}
#include<iostream>
#include<string>
using namespace std;
const int N = 100010;
int h[N], mysize, cnt; // cnt记录第k次插入
int ph[N], hp[N];
// ph[k]: 第k个插入 --> 下标
// hp[k]: 下标k -->第几个插入
// 交换堆中的两个数,那么下标和第几次插入的关系也要变化
void swap_heap(int a, int b) {
swap(ph[hp[a]], ph[hp[b]]);
swap(hp[a], hp[b]);
swap(h[a],h[b]); // 交换数值
}
void down(int i) {
int t = i;
if(2 * i <= mysize && h[2 * i] < h[t]) t = 2 * i;
if(2 * i + 1 <= mysize && h[2 * i + 1] < h[t]) t = 2 * i + 1;
if(t != i) {
swap_heap(t, i);
down(t);
}
}
void up(int i) {
while(i / 2 && h[i] < h[i / 2]) {
swap_heap(i, i / 2);
i /= 2;
}
}
// 插入一个数,在最后插入
void insert(int x) {
mysize++; // 记录下标
cnt++; // 第几次插入
ph[cnt] = mysize, hp[mysize] = cnt;
h[mysize] = x;
up(mysize);
}
// 删除第k个插入数
void del(int k) {
k = ph[k]; // 第k个插入的数对应的下标
swap_heap(k, mysize);
mysize--;
down(k);
up(k);
}
// 删除最小值,堆顶
void Del() {
swap_heap(1, mysize);
mysize--;
down(1);
}
// 修改第k个插入的数
void change(int k, int x) {
k = ph[k]; // 第k个插入的数对应的下标
h[k] = x;
down(k);
up(k);
}
int main() {
int n;
cin >> n;
string op;
int x, y;
while(n--) {
cin >> op;
if(op == "I") {
cin >> x;
insert(x);
}else if(op == "PM") {
cout << h[1] << endl;
}else if(op == "D") {
cin >> x;
del(x);
}else if(op == "DM") {
Del();
}else {
cin >> x >> y;
change(x, y);
}
}
return 0;
}