二叉堆 Heap
闲话
二叉堆是一种基础数据结构,主要应用于维护一组数据中的最大最小值。C++ 的STL中的优先队列就是使用二叉堆。
一.堆的性质
-
堆是一颗完全二叉树
-
堆的顶端一定是“最大”,最小”的,但是要注意一个点,这里的大和小并不是传统意义下的大和小,它是相对于优先级而言的,当然你也可以把优先级定为传统意义下的大小,但一定要牢记这一点,初学者容易把堆的“大小”直接定义为传统意义下的大小,某些题就不是按数字的大小为优先级来进行堆的操作的
-
堆一般有两种样子,小根堆和大根堆,分别对应第二个性质中的“堆顶最大”“堆顶最小”,对于大根堆而言,任何一个非根节点,它的优先级都小于堆顶,对于小根堆而言,任何一个非根节点,它的优先级都大于堆顶(这里的根就是堆顶啦qwq)
二.堆的数组模拟实现
#include <bits/stdc++.h>
#define re register
using namespace std;
const int N=1e5+5;
int n;
int tot=0;
int heap[N]; // 大根堆
inline void swap(int& a, int& b){
a=a^b;
b=a^b;
a=a^b;
}
// 向上调整
void up(int p){
while(p>1){
if(heap[p]>heap[p>>1]){
swap(heap[p],heap[p>>1]);
p>>=1;
}else break;
}
}
// 插入值
void insert(int x){
heap[++tot]=x;
up(tot);
}
// 取出堆顶
int getTop(){
return heap[1];
}
// 向下调整
void down(int p){
int s=p<<1;
while(p<=tot){
if(s<tot && heap[s+1]>heap[s]) s++; // 取子节点较大的那个
if(heap[p]<heap[s]){
swap(heap[p],heap[s]);
p=s; s=p<<1;
}
}
}
// 删除堆顶
void extract(){
heap[1]=heap[tot--];
down(1);
}
// 删除节点
void remove(int p){
heap[p]=heap[tot--];
up(p); down(p);
}
int main()
{
ios::sync_with_stdio(0);
clock_t c1 = clock();
#ifdef LOCAL
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
#endif
// ======================================================================
cin>>n;
for(re int i=0;i<n;i++){
re int tmp; cin>>tmp;
insert(tmp);
}
// ======================================================================
end:
cerr << "Time Used:" << clock() - c1 << "ms" << endl;
return 0;
}
三.堆的 STL 实现
牢骚
这年头真的没几个人写手写堆(可能有情怀党?)
一是手写堆容易写错代码又多,二是STL 直接给我们提供了一个实现堆的简单方式:优先队列
手写堆和STL的优先队列有什么 区别?没有区别
代码实现难度方面:优先队列完爆手写堆
这两方面综合起来,一般都是用STL的优先队列来实现堆
咳咳
定义一个优先队列:
首先你需要一个头文件:
#include<queue>
然后
priority_queue<int> q;//这是一个大根堆q
priority_queue<int,vector<int>,greater<int> >q;//这是一个小根堆q
//注意某些编译器在定义一个小根堆的时候greater<int>和后面的>要隔一个空格,不然会被编译器识别成位运算符号>>
优先队列的操作:
q.top()//取得堆顶元素,并不会弹出
q.pop()//弹出堆顶元素
q.push()//往堆里面插入一个元素
q.empty()//查询堆是否为空,为空则返回1否则返回0
q.size()//查询堆内元素数量
常用也就这些,貌似还有其他,不过基本也用不到,知道上面那几个也就可以了
不过有个小问题就是STL只支持删除堆顶,而不支持删除其他元素
但是问题不大,开一个数组 del
,在要删除其他元素的时候直接就标记一下 del[i]=1
,这里的下标是元素的值,然后在查询的时候碰到这个元素被标记了直接弹出然后继续查询就可以了
四.堆的运算符重载
一般 priority_queue
用自己定义的结构体类型时, 都要重载 <
运算符, 便于内部二叉堆排序比较
大根堆
// 优先队列元素
struct node{
int ver; // 顶点编号
ll dis; // 到 s 的距离, 用于小根堆排序
// 重载 < 运算符
bool operator <(const node& x) const{
return dis<x.dis; // 大根堆 所以是 <
}
};
priority_queue<node> pq; // 大根堆
小根堆
// 优先队列元素
struct node{
int ver; // 顶点编号
ll dis; // 到 s 的距离, 用于小根堆排序
// 重载 < 运算符
bool operator <(const node& x) const{
return dis>x.dis; // 小根堆 所以是 >
}
};
priority_queue<node> pq; // 小根堆