如何手写一个堆?
性质:
堆的结构是一棵完全二叉树
小根堆:每个节点小于等于左右孩子节点(递归定义),因此根节点就是最小值。
大根堆与之相反 。
堆的存储:
用一维数组存储,根节点下标为1,x的左孩子为2x,右孩子为2x + 1。
(下标从1开始)
基本操作:
down(x) 向下调整
up(x) 向上调整
常见应用:
1.插入一个数
在整个堆的最后一个位置插入x, heap[++size] = x;
然后将这个数x不断往上移动,up(x);
2.求集合中的最小值
小根堆的根节点就是最小值 heap[1];
3.删除最小值
将堆的最后一个元素覆盖到堆顶,然后删除最后一个节点,让新的根节点向下调整
heap[1] = heap[size],size -- ;
down(1);
4.删除任意一个元素
heap[k] = heap[size],size -- ;
down(k); up(k); (执行其中一个)
5.修改任意一个元素
heap[k] = x;
down(k); up(k);
模板
//向下调整堆
void down(int u)
{
int t = u;
if(2*u <= cnt && h[2*u] < h[t]) t = 2*u;
if(2*u + 1 <= cnt && h[2*u + 1] < h[t]) t = 2*u + 1;
if(u != t)
{
swap(h[u],h[t]);
down(t);
}
}
//向上调整堆
void up(int u)
{
while(u / 2 && h[u/2] > h[u])
{
swap(h[u],h[u/2]);
u /= 2;
}
}
带有映射关系的模板
int h[N], ph[N], hp[N], size;
// ph[k]存储第k个插入的点在堆中的位置(将下标映射到堆)
// hp[k]存储堆中下标是k的点是第几个插入的(将堆映射到下标)
// O(n)建堆
for (int i = n / 2; i; i -- ) down(i);
// 交换两个点,及其映射关系
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(u, t);
down(t);
}
}
void up(int u)
{
while (u / 2 && h[u] < h[u / 2])
{
heap_swap(u, u / 2);
u /= 2;
}
}
典型——AcWing.838 堆排序
#include <iostream>
using namespace std;
const int N = 100010;
int m,n;
int h[N],cnt;
void down(int u)
{
int t = u;
if(2*u <= cnt && h[2*u] < h[t]) t = 2*u;
if(2*u + 1 <= cnt && h[2*u + 1] < h[t]) t = 2*u + 1;
if(u != t)
{
swap(h[u],h[t]);
down(t);
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n ; i ++ )
cin >> h[i];
cnt = n;
for(int i = n/2; i >= 1; i -- ) //从n/2开始down 这样建堆,时间复杂度为O(n)
down(i);
while(m -- )
{
cout << h[1] << ' ';
h[1] = h[cnt];
cnt -- ;
down(1);
}
return 0;
}
典型——AcWing 839.模拟堆
维护一个集合,初始时集合为空,支持如下几种操作:
- “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≤1051≤N≤105
−109≤x≤109−109≤x≤109
数据保证合法。
输入样例:
8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM
输出样例:
-10
6
#include <iostream>
#include <string.h>
using namespace std;
const int N = 100010;
int h[N],ph[N],hp[N],cnt;
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 <= cnt && h[2*u] < h[t]) t = 2*u;
if(2*u + 1 <= cnt && h[2*u + 1] < h[t]) t = 2*u + 1;
if(t != u)
{
heap_swap(t,u);
down(t);
}
}
void up(int u)
{
while(u/2 && h[u/2] > h[u])
{
heap_swap(u,u/2);
u /= 2;
}
}
int main()
{
int n, m = 0;
int x,k;
cin >> n;
while( n -- )
{
string op;
cin >> op;
if(op == "I")
{
cin >> x;
cnt ++ ;
m ++ ;
ph[m] = cnt,hp[cnt] = m;
h[cnt] = x;
up(cnt);
}
else if(op == "PM")
cout << h[1] << endl;
else if(op == "DM")
{
heap_swap(1,cnt);
cnt -- ;
down(1);
}
else if(op == "D")
{
cin >> k;
k = ph[k];
heap_swap(k,cnt);
cnt -- ;
down(k),up(k);
}
else
{
cin >> k >> x;
k = ph[k];
h[k] = x;
down(k),up(k);
}
}
return 0;
}