优先队列
百度百科:普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。通常采用堆数据结构来实现。
简单来说优先队列的队头是你所设定优先级最高的元素(最大、最小),这一点其实和排序很像,但是优先队列的特点是动态加数据时算法复杂度只需要O(logn),而如果每次加入一个数重新排序的话需要O(nlogn)的。为了让大家更好的理会这一点,引入一道例题
应用场景
在一个果园里,达达已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。
达达决定把所有的果子合成一堆。
每一次合并,达达可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。
可以看出,所有的果子经过 n−1 次合并之后,就只剩下一堆了。
达达在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以达达在合并果子时要尽可能地节省体力。
假定每个果子重量都为 1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使达达耗费的体力最少,并输出这个最小的体力耗费值。
分析
每次挑选两堆合并,选择花费最小的,直觉上来讲,每次选择所有堆里最轻的两堆合并即可,这个直觉也是对的,给出简单证明因为将n堆果子合成一堆一定需要n-1次操作,且每堆所做的贡献为被合并的次数乘上自身的值,这是不是有点像霍夫曼编码问题,重量就是叶子节点参与合并的次数为跟到节点的高度,理所应当的,要把最轻的放在下面来减少体力耗费。
实际操作
现在我们要做的是每次选择最轻的两堆,合并成新的一堆,然后加入集合中,重复下去得到剩一堆,自然你可以每次排序取得前两个值,然后相加加入数组中,但是这样算法复杂度将会达到O(n2logn),而采取优先队列你只需要O(nlogn)就可以实现这一过程。那废话不多说来看看代码实现
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N=10100;
int n,m,i,j,a[N],ans;
priority_queue<int,vector<int>,greater<int> > p;//小根堆,堆顶最小
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i],p.push(a[i]);//加入堆操作
for(int i=1;i<n;i++)
{
int x,y;
x=p.top();//取个最小的
p.pop();//弹出
y=p.top();//那么剩下一个就是次小的
p.pop();//再弹出
ans+=x+y;//记录花费
p.push(x+y);//合并加入集合中
}
cout<<ans;
return 0;
}
语法分析
priority_queue<int,vector<int>,greater<int> /less<int>> p;
priority_queue<datetype,vector<datetype>,cmp>;
熟悉sort的同学可能会对这块接受较快一些,首先定义一个优先队列你需要确定数据类型以及容器(不懂就先背过),小根堆为greater,大根堆为less,配合对应的数据类型使用即可
//三个常用操作,顾名思义就不多加叙述了
q.push();
q.pop();
q.top();
自定义排序
那么要如何来自定义优先级呢,比方说一个物品有过期时间与价值,我希望让过期时间短的排在前面,过期时间相同的价值大的排前面
那么我们可以使用一个结构体
struct node{
int w,t;
bool operator <(const node &x)const{//特定语法格式不懂可以先背过,先把代码写出来能跑就行,后续再了解具体名词
if(t==x.t)return w>x.w;//价值大就用大于号
return t<x.t;//时间小就用小于号
}
}
priority_queue<node,vector<node>>;//换成相应数据类型
底层实现
这一块内容可以prority_queue作为已经封装好的STL是基本够用的,但是了解其底层实现原理也是十分重要这里推荐OI WIki