一、堆 概念
堆是一棵树,每个节点都有一个键值;
每个节点的键值 >= 父亲节点的键值,叫小根堆;每个节点的键值 < 父亲节点的键值,叫大根堆
(小根)堆主要支持的操作有:插入一个数、查询最小值、删除最小值,合并两个堆、减小一个元素的值
(表中“支持可持久化”是指可以对任意的历史版本进行查询或操作,产生新版本)
通常情况下,“堆”指的是二叉堆
二、二叉堆
二叉堆其实是一棵满足“堆性质”(大、小根堆)的完全二叉树
直接使用一个数组来保存二叉堆,采用层次序列存储方式,即逐层从左到右为树中的节点依次编号,把此编号作为节点在数组中存储的位置即下标。
插入操作 insert
向二叉堆插入一个带有权值val的新节点:时间复杂度为堆的深度,即O(log N)
- 把新节点直接放在存储二叉堆的数组末尾
- 然后通过交换的方式向上调整,直至满足堆的性质
int heap[SIZE],n;
void up(int p){//把下标编号为p的数 向上调整
while(p>1){
if(heap[p]>heap[p/2]){//子节点>父亲节点,不满足大根堆性质
swap(heap[p],heap[p/2]);
p/=2;
}
else break;
}
}
void insert(int val){//插入值为val的数
heap[++n]=val;
up(n);
}
删除堆顶操作 extract
时间复杂度为堆的深度,即O(log N)
- 把堆顶heap [1] 与存储在数组末尾的节点 heap [n] 交换
- 然后移除数组末尾的节点 n–
- 把堆顶通过交换的方式向下调整,直至满足堆的性质
void down(int p){//向下调整
int s=p*2;//p的左子节点
while(s<=n){
if(s<n&&heap[s]<heap[s+1]) s++;//左右子节点取较大者
if(heap[s]>heap[p]){//子节点>父亲节点,不满足大根堆性质
swap(heap[s],heap[p]);
p=s,s=p*2;
}
}
}
void extract(){ //删除堆顶操作
heap[1]=heap[n--];
down(1);
}
删除操作 remove
把存储在数组下标为p的位置的节点从二叉堆里删除 remove§,与extract 类似,不同的是先把heap [p] 与存储在数组末尾的节点 heap [n] 交换,
注意,此时heap [p] 可能需要向下调整,也可能需要向上调整,需要分别进行检查和处理;
void remove(int p){ //删除下标为p的位置的节点操作
heap[p]=heap[n--];
up(p),down(p);
}
三、《算法进阶指南》例题
145. 超市
链接: 145. 超市
/*贪心+小根堆*/
#include<bits/stdc++.h>
using namespace std;
int n;
int main(){
while(cin>>n){
vector<pair<int,int> >product(n);
for(int i=0;i<n;i++){
cin>>product[i].second>>product[i].first;
}
sort(product.begin(),product.end());//先按照product[i].first排序 ;product[i].first相同,再按 product[i].second排
/*for(int i=0;i<n;i++){
cout<<product[i].second<<' '<<product[i].first<<endl;
}*/
priority_queue<int,vector<int>,greater<int> >heap;
for(auto p:product){//贪心:动态维护一个小根堆,
heap.push(p.second);
if(heap.size()>p.first) heap.pop();//如果当前的小根堆存储的商品数量>天数(也就是应该要卖的商品数量) 就把利润最小的商品删除掉
}
/*while(heap.size()){
cout<<heap.top()<<endl;heap.pop();
} */
int ans=0;
while(heap.size()){//小根堆里剩余的就是要卖的商品
ans+=heap.top();heap.pop();
}
cout<<ans<<endl;
}
}