基本思想:
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<=K2i+2 ,则称为小堆(或大堆)
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆
而且堆的性质满足:
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
借鉴小根堆大根堆以及对顶堆的示意图,对顶堆是下面为大根堆,上面为小根堆,元素向上递增,这样我们就可以用O(1)的时间复杂度维护中位数
这里y总讲的是我们用数组来手写一个堆,较大的元素下沉,较小的元素上升,不断维护堆里元素保证堆顶为最小的元素即可。
stl容器里分别是大根堆 priority_queue <int,vector<int>,less<int> >q;
小根堆 priority_queue <int,vector<int>,greater<int> > q;
输入一个长度为 n 的整数数列,从小到大输出前 m 小的数。
输入格式
第一行包含整数 n 和 m。
第二行包含 n 个整数,表示整数数列。
输出格式
共一行,包含 m 个整数,表示整数数列中前 m 小的数。
数据范围
1≤m≤n≤1e5
1≤数列中元素≤1e9
输入样例:
5 3
4 5 1 3 2
输出样例:
1 2 3
#include<iostream> #include<algorithm> using namespace std; const int N = 100010; int n, m; int h[N], size1; void down(int u) { int t = u; //用t存左右儿子是否有小于该节点的 if(u * 2 <= size1 && h[u * 2] < h[t]) t = u * 2; //左儿子存在且小于该节点 if(u * 2 + 1 <= size1 && h[u * 2 + 1] < h[t]) t = u * 2 + 1; //右儿子存在且小于该节点 if(t != u) //不等说明存在儿子比该节点小 就要进行交换并继续递归 { swap(h[u], h[t]); down(t); } } int main() { cin >> n >> m; for(int i = 1; i <= n; i++) cin >> h[i]; size1 = n; for(int i = n / 2; i; i--) down(i); //完全二叉树的最后一层叶节点有n/2个,所以我们只用down除叶节点以外的n/2个节点 //将建堆的复杂度降低到O(n) while(m--) { cout << h[1] << " "; h[1] = h[size1]; size1--; down(1); } return 0; }
例题:
373. 查找和最小的 K 对数字 - 力扣(LeetCode)
480. 滑动窗口中位数 - 力扣(LeetCode)//对顶堆 动态维护中位数