例题:(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();
}
};