算法竞赛入门——优先队列

优先队列

百度百科:普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值