首先讲一下能够实现的操作。
- 插入一个数
- 查找最小值
- 删除最小值
- 删除任意一个元素
- 修改任意一个元素
什么是堆?
堆其实是一棵完全二叉树。
即处理叶子节点和倒数第一层节点,其他节点都有两个子节点,而且顺序是从上到下,从左到右。
小根堆:每个节点都满足小于等于左右儿子节点。
堆有down和up操作。
何为down?
何为up?
操作
插入一个数 | heap[++size]=x;up(size)在尾部插入,后维护 |
查找最小值 | head[1]; |
删除最小值 | heap[1]=heap[size];size--;down(1) |
删除任意一个元素 | heap[k]=heap[size];size--;down(k);up(k) |
修改一个元素 | heap[k]=x;down(k);up(k) |
例题:
输入一个长度为 𝑛 的整数数列,从小到大输出前 𝑚 小的数。
输入格式
第一行包含整数 𝑛 和 𝑚。
第二行包含 𝑛 个整数,表示整数数列。
输出格式
共一行,包含 𝑚 个整数,表示整数数列中前 𝑚 小的数。
数据范围
1≤m≤n≤1e5,1≤𝑚≤𝑛≤1e5,
1≤数列中元素≤1e9,1≤数列中元素≤1e9
输入样例:
5 3
4 5 1 3 2
输出样例:
1 2 3
代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e6+10;
int heap[N],idx;
void down(int u){int t =u;//假设t是在此节点和其左右儿子节点中最小的,初始时将t赋值为u(此节点)。if(2*u<=idx && heap[2*u]<heap[t]) t = 2*u;if(2*u+1 <= idx && heap[2*u+1]<heap[t]) t = 2*u+1;if(u != t){swap(heap[u],heap[t]);down(t);}
}int main(){int n,m;scanf("%d%d",&n,&m);idx=n;for(int i=1;i<=n;i++){scanf("%d",&heap[i]);}for(int i=n/2;i>=1;i--){down(i);}while(m--){printf("%d ",heap[1]);heap[1]=heap[idx];idx--;down(1);}return 0;
}
单独拿出down和up操作
down:
void down(int u){int t =u;//假设t是在此节点和其左右儿子节点中最小的,初始时将t赋值为u(此节点)。if(2*u<=idx && heap[2*u]<heap[t]) t = 2*u;if(2*u+1 <= idx && heap[2*u+1]<heap[t]) t = 2*u+1;if(u != t){swap(heap[u],heap[t]);down(t);}
}
//注意u是当前节点,在堆上的索引,idx是当前已经用的索引的个数(相当于每个元素的身份证编号,从小到大开始编号)。
//2*u表示当前节点u的左儿子节点的身份证号,heap[2*u]表示左儿子节点的值。2*u<=idx表示存在左儿子,
//因为如果计算出来的身份证编号比现在有的最大的编号还大,就说明计算出来的编号不存在。
up:
void up(int u){while( u/2 >0 && heap[u] < heap[u/2]){swap(heap[u],heap[u/2]);u=u/2;}
}
//u/2表示当前节点u的父节点的编号,一个节点只有一个父节点