例题:(1)AcWing 838. 堆排序
我们可以利用一个一维数组来模拟堆。由于堆本质上是一个完全二叉树,他的每个父节点的权值都小于左右子节点,而每个父节点编号为n时,左节点编号为2*n,右节点编号为2*n+1。用size记录堆的大小便于维护。在初始建立堆时,由于最后一层在前面每一层建立后自然而然就会被排序,因此我们只需要从倒数第二层开始建立到第一层即可。建立的过程利用到down操作,down操作本质上就是在父节点和两个子节点中进行比较换位,直到往下也不需要换位。而由于一维数组不方便删除头结点,但是删除尾节点只需要size--,因此我们删除头节点的操作就是把头结点和尾结点交换,然后把新的头节点down。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int heap[N],sz;
void down(int u){int t = u;if(u*2<=sz&&heap[u*2] < heap[t]) t = u*2;if(u*2+1<=sz&&heap[u*2+1] < heap[t]) t = u*2+1;if(t!=u){swap(heap[t],heap[u]);down(t);}
}
int main()
{int n,m;scanf("%d%d", &n, &m);for(int i = 1;i<=n;i++){scanf("%d", &heap[i]);}sz = n;for(int i = n/2;i;i--){down(i);}while(m--){printf("%d ",heap[1]);heap[1] = heap[sz--];down(1);}return 0;
}
(2)AcWing 839. 模拟堆
模拟堆的过程相比上面多了几个操作:(1)删除第k个数,修改第k个插入的数,需要用一个记录下标-第k个插入的数和相反关系的两个数组进行维护。(2)还需要用up把下面的较小数往上移。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
unordered_map<int,int> id,mp;//id的对应关系:下标-第k个插入的数,mp的对应关系: 第k个插入的数 - 下标
int h[N];
int sz,cnt;
void down(int u){int t = u;if(u*2<=sz&&h[u*2]<h[t]) t = u*2;if(u*2+1<=sz&&h[u*2+1]<h[t]) t = u*2+1;if(t!=u){swap(mp[id[u]],mp[id[t]]);swap(id[u],id[t]);swap(h[u],h[t]);down(t);}
}
void up(int u){int t = u;if(u/2&&h[u/2]>h[t]) t = u/2;if(t!=u){swap(mp[id[u/2]],mp[id[u]]);swap(id[u/2],id[u]);swap(h[u/2],h[u]);up(t);}
}
int main()
{int n;scanf("%d", &n);for(int i = 0;i<n;i++){char op[5];scanf("%s",op);if(!strcmp(op,"I")){int x;scanf("%d", &x);h[++sz] = x;mp[++cnt] = sz;id[sz] = cnt;up(sz);}else if(!strcmp(op,"PM")){printf("%d\n",h[1]);}else if(!strcmp(op,"DM")){swap(mp[id[1]],mp[id[sz]]);swap(id[1],id[sz]);swap(h[1],h[sz]);sz--;down(1);}else if(!strcmp(op,"D")){int k;scanf("%d", &k);k = mp[k];swap(mp[id[k]],mp[id[sz]]);swap(id[k],id[sz]);swap(h[k],h[sz]);sz--;down(k);up(k);}else{int x,k;scanf("%d%d", &k, &x);k = mp[k];h[k] = x;down(k);up(k);}}return 0;
}
练习:(1)Leetcode 692.前k个高频单词 没做出来。。优先队列居然还能这么用的吗。。
先用哈希表统计每个单词出现的次数,然后遍历哈希表(可以用auto直接循环),把每个<string,pair>加入到优先队列进行排序。最后由于需要按照字典序排序,我们还需要从后往前放置答案。
class Solution {
public:vector<string> topKFrequent(vector<string>& words, int k) {unordered_map<string,int> x;for(auto c:words){x[c]++;}auto cmp = [](const pair<string,int> &a,pair<string,int> &b){return a.second == b.second ? a.first < b.first : a.second > b.second;};priority_queue<pair<string,int>,vector<pair<string,int>>, decltype(cmp)> ans(cmp);for(auto c:x){ans.push(c);if(ans.size()>k) ans.pop();}vector<string> res(k);for(int i = k-1;i>=0;i--){res[i]=ans.top().first;ans.pop();}return res;}
};
(2)Leettcode 703.数据流中的第K大元素
一开始一直想靠sort。。没用。。
由于优先队列只能访问队头数组,所以我们要让数据流中的第K大元素是优先队列的队头,因此我们要保证优先队列中只有K个数,这K个数中的最小的数就一定是第K大的数,也是优先队列的队头。
class KthLargest {
public:priority_queue<int,vector<int>,greater<int>> x;int k;KthLargest(int k, vector<int>& nums) {this->k = k;for(auto c:nums){x.push(c);if(x.size()>k) x.pop();}}int add(int val) {x.push(val);if(x.size()>k) x.pop();return x.top();}
};
(3) Leetcode 264.丑数II
没做出来。。
假如一个数为丑数,那么这个数的两倍,三倍和五倍对应的数也是丑数,当然这么做肯定会有重复,因此我们用一个哈希集合来存储出现过的数,这样保证小根堆里面不出现重复的数,因此第k次取出来的数就是第k个丑数。
class Solution {
public:long nthUglyNumber(int n) {priority_queue<long,vector<long>,greater<long>> x;unordered_set<int> y;x.push(1);y.insert(1);for(int i = 0;i<n-1;i++){long k = x.top();x.pop();if(!y.count(k*2)){x.push(k*2);y.insert(k*2);}if(!y.count(k*3)){x.push(k*3);y.insert(k*3);}if(!y.count(k*5)){x.push(k*5);y.insert(k*5);}}return x.top();}
};