堆与优先队列

阅读本章前,请先确保你懂得:

  1. 完全二叉树如何用数组存储
  2. 完全二叉树的深度

如果让你在一个数组中寻找最大值,你会怎么做?

\(n \le 10^5\)

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int main(){
	int n,ans=0;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		ans=max(ans,a[i]);
	}
	cout<<ans;
	return 0;
}

相信这个解法十分简单,那如果再加入 \(q\) 次插入操作呢?

\(q \le 10^5\)

也很简单,只需要不断的用原来数组中的最大值与新插入的数据比较就好了。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int main(){
	int n,ans=0;
	cin>>n>>q;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		ans=max(ans,a[i]);
	}
	while(q--){
		int x;
		cin>>x;
		ans=max(ans,x);
	}
	cout<<ans;
	return 0;
}

但是如果再加入删除最大值的操作呢?

此时情况就麻烦了,删除操作必须实现 \(O(n^2)\) 算法,而且此时插入操作也变成了 \(O(n^2)\),因为我们不能确定原数组中的最大值有没有重复。

所以总体的时间复杂度变成了 \(O(qn^2),明显超时\)

那有没有一种更优的算法呢?

当然有,那就是堆。

洛谷模板题

如题目中所见

给定一个数列,初始为空,请支持下面三种操作:

  1. 给定一个整数 \(x\),请将 \(x\) 加入到数列中。
  2. 输出数列中最小的数。
  3. 删除数列中最小的数(如果有多个数最小,只删除 \(1\) 个)。

首先介绍一下堆这种数据结构。

堆是一种完全二叉树状的结构,无论如何总是将最值放在根节点的位置,这也就使得他的插入与删除操作都是 \(O(nlog_n)\) 的,而查询则是 \(O(1)\) 的。

既然最值永远在根的位置,那么每一棵子树也是如此。

插入操作

可以将插入的操作直接接到整棵树的最后,然后不断与根节点比较,如果比根节点还小,替换根节点,持续向上。

void up(int x){
	while(x/2>=1&&h[x]<h[x/2]){//x/2代表x的根节点
		swap(h[x],h[x/2]);
		x/=2;
	}
}
void push(int x){
	h[++idx]=x;//idx表示当前的节点总数
	up(idx);
}

删除操作

直接将根节点放到最后,在不断选择根节点,直到将整棵树补充完整。

void down(int x){
	int t=x;
	if(2*x<=idx&&h[2*x]<h[t]) t=2*x;//比较左节点
	if(2*x+1<=idx&&h[2*x+1]<h[x]) x=2*x+1;//比较右节点
	if(t!=x){
		swap(h[x],h[t]);
		down(t);//递归执行
	}
}
void pop(){
	h[1]=h[idx--];//相当于删除根节点
	down(1);
}

查询

直接输出 \(h_1\)即可。

cout<<h[1];

于是完整代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int h[N],idx,n;
void down(int x){
	int t=x;
	if(2*x<=idx&&h[2*x]<h[t]) t=2*x;
	if(2*x+1<=idx&&h[2*x+1]<h[x]) x=2*x+1;
	if(t!=x){
		swap(h[x],h[t]);
		down(t);
	}
}
void up(int x){
	while(x/2>=1&&h[x]<h[x/2]){
		swap(h[x],h[x/2]);
		x/=2;
	}
}
void push(int x){
	h[++idx]=x;
	up(idx);
}
void pop(){
	h[1]=h[idx--];
	down(1);
}
int main(){
	cin>>n;
	while(n--){
		int op,x;
		cin>>op;
		if(op==1){
			cin>>x;
			push(x);
		}
		if(op==2){
			cout<<h[1];
		}
		if(op==3){
			pop();
		}	
	}	
	return 0;
}

优先队列

类似 queue,写作 priority_queue
手写堆太麻烦了,有没有更加便捷的方式?
C++ 的 STL 里有已经帮我们写好的堆模板,就是优先队列。
优先队列的操作很简单,和队列的操作是一样的。

  • priority_queue<T> q : 创建一个类型为 T,名字为 q 的优先队列。
  • .push(x) : 在队列里插入一个元素。
  • .pop() : 在队列里删除一个元素。
  • .top() : 返回优先队列里优先级最高的元素。
  • .size() : 返回队列长度。
  • .empty() : 若队列为空返回 true,否则返回 false

优先队列默认为大根堆(即堆的根为数列中的最大值)如果要改成小根堆,那么需要加入一些参数。
priority_queue<T,vector<T>,greater<T>> qpriority_queue<T,deque<T>,greater<T>> q 可以创建一个从大到小排序的堆。

greater<T> 能够将一个容器或函数的功能翻转,例如 sort 本来是从小到大排序,而 sort(a+1,a+n+1,greater<int>()) 就能够实现从大到小排序。

结构体类型的优先队列

结构体有多个成员,因此难以有明确的最小值判断标准,这时就需要我们人为的定义小于号的定义,需要用到重载运算符:

struct node{
	int a,b;//例如用a作为关键字比较
	bool operator < (const node &x,const node &y){
		return x.a<y.a;
	}
}

然后再直接使用优先队列即可。

posted @   very_easy  阅读(20)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示