堆
1.插入一个数
添加头结点比较困难,添加尾结点十分容易
heap[++size]=x;
up(size);
2.求集合中的最小值
heap[1];
3.删除最小值
删除头结点非常困难,删除尾结点非常方便
heap[1]=heap[size];
size--;
down(1);
4.删除任意元素
heap[k]=heap[size];
size--;
down(k);//只有heap[k]变大时执行
up(k);//只有heap[k]变小时执行
5.修改任意元素
heap[k]=x;
down(k);
up(k);
注意:
1.下标从1开始,比较方便,x=0时,2x=0;
2.4,5仅数组模拟的堆可实现,STL实现不了)
堆是一棵完全二叉树
小根堆:每一点小于等于左右儿子
堆的存储结构:
1是根结点
对于结点x:
左儿子为2x,右儿子为2x+1
基本操作:
1.up
2.down
两个操作的时间复杂度都与树的高度有关logn
void down(int k){
}
建堆:
法1.一个一个往里插,总共时间复杂度O(nlogn)
法2.从n/2down到1,时间复杂度为O(n)
for(int i=n/2;i>=1;i--){
down(i);
}
ph[k]第k个插入的点在堆里的下标
hp[j]堆里j点是第几个插入的点
ph[k]=j;
hp[j]=k;
例题:堆排序
#include <iostream>
#include <algorithm>
using namespace std;
const int N=100010;
int h[N],s;
void down(int i){
int u=i;
if(2*i<=s&&h[u]>h[2*i]){
u=2*i;
}
if(2*i+1<=s&&h[u]>h[2*i+1]){
u=2*i+1;
}
if(u!=i){
swap(h[u],h[i]);
down(u);
}
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
s=n;
for(int i=1;i<=n;i++){
scanf("%d",&h[i]);
}
for(int i=n/2;i>=1;i--){
down(i);
}//初始化堆
while(m--){
printf("%d ",h[1]);
h[1]=h[s];
s--;
down(1);
}
return 0;
}
例题:模拟堆
#include <iostream>
#include <cstring>
using namespace std;
const int N=100010;
int h[N],hp[N],ph[N];
//ph[k]第k个插入的值的下标
//hp[i]下标为i的值是第k个插入的
int s=0;
void swap_(int l,int r){
swap(h[l],h[r]);
swap(hp[l],hp[r]);
swap(ph[hp[l]],ph[hp[r]]);
}
void down(int i){
int u=i;
if(2*i<=s&&h[u]>h[2*i])u=2*i;
if(2*i+1<=s&&h[u]>h[2*i+1])u=2*i+1;
if(u!=i){
swap_(u,i);down(u);
}
}
void up(int i){
while(i/2&&h[i]<h[i/2]){
swap_(i,i/2);
i=i/2;
}
}
int main(){
int n,m=0;//m表示当前是第几个插入的数
scanf("%d",&n);
while(n--){
char op[5];
scanf("%s",op);
if(!strcmp(op,"I")){
int x;
scanf("%d",&x);
m++;
s++;
h[s]=x;
hp[s]=m;
ph[m]=s;
up(s);
}else if(!strcmp(op,"D")){
int k;
scanf("%d",&k);
k=ph[k];//转化成堆里的下标
swap_(k,s);
s--;
up(k),down(k);//这两个函数最多只会执行其中一个
}else if(!strcmp(op,"C")){
int k,x;
scanf("%d%d",&k,&x);
k=ph[k];
h[k]=x;
down(k), up(k);
}else if(!strcmp(op,"PM")){
printf("%d\n",h[1]);
}else{
swap_(1,s);
s--;
down(1);
}
}
return 0;
}