C++——优先级队列(priority_queue)的使用及实现
目录
一.priority_queue的使用
1.1、基本介绍
1.2、优先级队列的定义
1.3、基本操作(常见接口的使用)
1.4、重写仿函数支持自定义数据类型
二.priority_queue的模拟实现
2.1、构造&&重要的调整算法
2.2、常见接口的实现
push()
pop()
top()
empty()、size()
三.利用仿函数改进调整算法
一.priority_queue的使用
1.1、基本介绍
我们之前讲过数据结构中的队列,它具有先进先出的特性(FIFO).添加元素时只能在队尾插入,删除元素时只能删除队首的元素.
而优先级队列,它并不满足先进先出的特性,倒像是数据结构中的“堆”. 优先级队列每次出队时只能是队列中优先级最高的元素,而不是队首的元素。
这个优先级可以通过元素的大小,或者赋值运算符重载等进行比较. 例如定义元素越大,优先级越高,那么每次出队的时候一定是队列中最大的元素,因为它的优先级最高.并且重新进行维护.
经过上述的说明,是不是和我们所说的“堆”很相似,优先级队列的内部确实是由堆结构实现.
下面是官方文档的一段介绍:
1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。
3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
1.2、优先级队列的定义
首先,使用优先级队列,需要包含头文件<queue>,priority_queue的定义如下:
template <class T, class Container = vector<T>, class Compare = less<typename Container::value_type> > class priority_queue;第一个模板参数为为class T,代表每个元素的类型.
第二个模板参数为class Container,缺省值为vector<T>,代表存储这些数据的容器,可以是vector,deque等,但不能是list,因为它的内部空间不连续.
第三个模板参数为class Compare,缺省值为less<T>,其中less是个仿函数,是降序排序,既优先级最大的是容器中最大的元素.又叫比较函数.
当然可以升序排序,把less改为greater即可.
less 和 greater使用的前提是建立在这些数据类型是C++基本的数据类型.
例如下面这段代码:
//不写后面两个参数默认为vector,lesspriority_queue<int> pq1;//建立一个优先级队列(大堆),数据类型是int,利用vector容器实现,less(降序)实现priority_queue<int, vector<int>, less<int>> pq2;//建立一个优先级队列(小堆),数据类型是int,利用vector容器实现,greater(降序)实现priority_queue<int, vector<int>, greater<int>> pq3;
1.3、基本操作(常见接口的使用)
它的操作与基本队列操作一样,主要有以下接口:
top() :返回元素中第一个元素的引用(优先级最高的元素都会被放到顶部,既第一个元素).
push():插入一个元素,并重新维护堆,无返回值.
pop() :删除优先级最高的元素,并重新维护堆无返回值
size() :返回容器中有效元素的数量,返回队列的大小
empty() :检测容器是否为空.返回“true”或者“false”.
代码示例:
int main()
{//不写后面两个参数默认为vector,lesspriority_queue<int> pq1;//push的使用pq1.push(1);pq1.push(2);pq1.push(3);pq1.push(4);pq1.push(5);//push完之后,维护也完毕,此时优先级最高的是元素是5,排在第一位cout << pq1.top() << endl;//优先级最高的一位,所以应该是5//pop的使用:删除一个优先级最高的元素5,此时重新调整,优先级最高的元素应该为4pq1.pop();cout << pq1.top() << endl;//size()的使用,删除了一个元素,此时应该还有四个元素cout << pq1.size() << endl;return 0;
}
执行结果:
正如我们预料所得.
1.4、重写仿函数支持自定义数据类型
仿函数是通过重载‘()’运算符来进行模拟函数操作的类.
通俗点说,仿函数是一个能行使函数功能类,然后类里必须实现“()”运算符重载.
比如我们要根据类里的某一个成员大小进行比较,因为是一个类,它不是C++里的基本数据类型,所以需要我们自己重新仿函数来支持它.
看下面这段程序
class Data
{
public:Data(int i, int d):id(d), data(d){}int GetData() const{return data;}
private:int id;int data;
};
//仿函数,从小到大排序,既大的优先级最高
class cmp
{
public:bool operator() (Data& d1, Data& d2){return d1.GetData() < d2.GetData();}
};
int main()
{//首先创建三个data类型数据Data* d1 = new Data(0, 1);Data* d2 = new Data(0, 2);Data* d3 = new Data(0, 3);//创建优先级队列,比较函数为cmp仿函数,并将数据全部pushpriority_queue<Data,vector<Data>,cmp> pq;pq.push(*d1);pq.push(*d2);pq.push(*d3);//全部输出出来while(!pq.empty()){cout << (pq.top().GetData()) << endl;pq.pop();}return 0;
}
二.priority_queue的模拟实现
2.1、构造&&重要的调整算法
priority_queue的底层结构就是堆,所以模拟实现只需要对堆封装即可.
所以其中大部分都是与之前堆的数据结构相关的一些方法.
我们知道priority_queue有三个参数来构造,所以我们也使用三个模板参数.
模板如下:
template<class T,class Container = vector<T>,class Compare = less<T>>
然后一个类须有成员变量,这个类里只有一个成员变量:
Container _con;
利用第二个模板参数既容器类型的构造了一个变量,这样就可以对里面的所有数据进行操作了.
准备就绪后,开始写构造函数,主要有两种:无参构造以及迭代器构造.
无参构造:其实可以不用写,但是由于有迭代器构造函数的存在,系统便不能再调用默认构造函数,所以必须自己手写一下无参的构造函数.
priority_queue(){}
迭代器构造
和之前的迭代器构造方法一样,看一下便知.
priority_queue(InputIterator first, InputIterator last){while (first != last){_con.push_back(*first);++first;}
于此同时,我们构造好数据后,还需要进行建堆,具体的建堆代码如下,和之前堆的数据结构中建堆的方法类似.
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)//[_con.size()-1]是最后一个元素的下标,再-1然后/2是计算中间的元素,既最后一个结点的父节点{adjust_down(i);}
既从数据中间的一个元素开始,每次进行向下调整算法,完毕之后--,指向下一个数据继续进行调整,如此直到第一个元素,建堆便完成.
提到了向下调整算法,这个方法在我之前堆的文章中有详细介绍过,大家可以去看之前的文章进行理解.
.
void adjust_down(size_t parent){size_t child = parent * 2 + 1;while (child < _con.size()){if (child + 1 < _con.size() && _con[child] < _con[child + 1])++child;if (_con[child] > _con[parent]){swap(_con[child], _con[parent]);parent = child;child = 2 * parent + 1;}elsebreak;}}
大概总固体思路是:先根据根节点找到孩子结点,然后判断左右两个孩子结点中的哪个大,默认是孩子结点是左孩子结点,如果右孩子结点比左孩子结点大,那么直接++即可,就是右孩子结点.
然后再判断,如果孩子结点的值大于父节点的值,则交换,并且更新父节点和孩子结点的值.
与此对应的是,既然有向下调整算法,那么也会有向上调整算法.
前面的文章也有写到过,不再详述.
void adjust_up(size_t child){size_t parent = (child - 1) / 2;while (child > 0){if (_con[child] > _con[parent]){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}elsebreak;}}
这个是传入孩子结点,我们根据公式求出父亲结点,公式就是代码中所写的那一个.
然后判断,符合条件更新父节点和子结点即可.
两大基础调整算法写完,那后面的就非常轻松了
2.2、常见接口的实现
这些接口都可以复用之前的调整算法.
push()
这个就是将数据插入,并且重新调整堆的结构.
void push(const T& x){_con.push_back(x);adjust_up(_con.size()-1);//传入最后一个数据的下标}
对于push_back(),有人可能有疑问包括我就是,我没有实现push_back(),但为什么可以直接写呢?
其实我们能用优先级队列的容器就那么几种,vector,deque等等,都是一些内部存储空间连续的,而这些容器都具有push_back()这个接口,所以到时候模板实例化的时候便可以用了.
pop()
先把首尾元素进行交换,然后删除最后一个元素,再进行向下调整.
void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}
top()
返回优先级最高既堆顶的元素,既容器的首元素.由于堆顶特性,堆中
const T& top(){return _con[0];}
empty()、size()
这些都是容器中所对应拥有的函数,直接返回即可.
bool empty() const{return _con.empty();}size_t size() const{return _con.size();}
三.利用仿函数改进调整算法
通过我们上面写的向上调整,向下调整算法,发现有一个比较麻烦的地方
就是它其中的每次比较都是大于,既每次都是大顶堆
但是如果我们想要小顶堆怎么办?只能一点一点的改大小于符号 ,很容易就会混和忘记,非常的不方便.
这个时候我们便可以使用仿函数来解决这个问题.这个时候便用到刚开始写的三个模板参数中的第三个参数了.
可以先写两个仿函数,一个用来构造小顶堆,另外的是大顶堆.
template<class T>class less{public:bool operator()(const T& l, const T& r){return l < r;}};template<class T>class greater{public:bool operator()(const T& l, const T& r){return l > r;}};
我们再把它应用到那两个调整算法里面.
adjust_up
最开始需要用仿函数构造一个对象,才可以使用.
Compare com;
原来其中的:
if (_con[child] > _con[parent])
改为:
if (com(_con[child] , _con[parent]))
adjust_down
最开始需要用仿函数构造一个对象,才可以使用.
Compare com;
原来其中的:
if (child + 1 < _con.size() && _con[child] < _con[child + 1])
if (_con[child] > _con[parent])
改为:
if (child + 1 < _con.size() && com(_con[child] , _con[child + 1]))
if (com(_con[child] , _con[parent]))
这样就改进完成了.以后想要改变大小顶堆时,只需要将Compare后面的仿函数改成自己需要的即可.
这就是优先级队列的所有内容了,包括使用及实现.
由于文章代码比较散乱,这里直接放上总代码方便参观.
#pragma once
#include<iostream>
#include<vector>using namespace std;
namespace hyx
{//大堆template<class T,class Container = vector<T>,class Compare = less<T>>class priority_queue{template<class T>class less{public:bool operator()(const T& l, const T& r){return l < r;}};template<class T>class greater{public:bool operator()(const T& l, const T& r){return l > r;}};public:priority_queue(){}template<class InputIterator>priority_queue(InputIterator first, InputIterator last){while (first != last){_con.push_back(*first);++first;}//建堆for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--){adjust_down(i);}}void adjust_up(size_t child){Compare com;size_t parent = (child - 1) / 2;while (child > 0){if (com(_con[child] , _con[parent])){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}elsebreak;}}void adjust_down(size_t parent){Compare com;size_t child = parent * 2 + 1;while (child < _con.size()){if (child + 1 < _con.size() && com(_con[child] , _con[child + 1]))++child;if (com(_con[child] , _con[parent])){swap(_con[child], _con[parent]);parent = child;child = 2 * parent + 1;}elsebreak;}}void push(const T& x){_con.push_back(x);adjust_up(_con.size()-1);}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}const T& top(){return _con[0];}bool empty() const{return _con.empty();}size_t size() const{return _con.size();}private:Container _con; };}
相关文章:

C++——优先级队列(priority_queue)的使用及实现
目录 一.priority_queue的使用 1.1、基本介绍 1.2、优先级队列的定义 1.3、基本操作(常见接口的使用) 1.4、重写仿函数支持自定义数据类型 二.priority_queue的模拟实现 2.1、构造&&重要的调整算法 2.2、常见接口的实现 push() pop() top() empt…...
Linux学习记录——십일 环境变量
文章目录1、认识2、通过代码获取环境变量1、手动获取2、函数获取3、重新认识环境变量1、认识 在云服务器上写程序时,最终的执行需要./文件名,点表示当前目录,/是文件分隔符,之后就会打印程序,这是用户的操作ÿ…...

【人工智能 Open AI 】我们程序员真的要下岗了- 全能写Go / C / Java / C++ / Python / JS 人工智能机器人
文章目录[toc]人工智能 AI Code 写代码测试用golang实现冒泡排序用golang实现计算环比函数goroutine and channel用golang实现二叉树遍历代码用golang实现线程安全的HashMap操作代码using C programming language write a tiny Operation Systemuse C language write a tiny co…...

STM32 EXTI外部中断
本文代码使用 HAL 库。 文章目录前言一、什么是外部中断?二、外部中断中断线三、STM32F103的引脚复用四、相关函数:总结前言 一、什么是外部中断? 外部中断 是单片机实时地处理外部事件的一种内部机制。当某种外部事件发生时,单片…...

Mapper代理开发——书接MaBatis的简单使用
在这个mybatis的普通使用中依旧存在硬编码问题,虽然静态语句比原生jdbc都写更少了但是还是要写,Mapper就是用来解决原生方式中的硬编码还有简化后期执行SQL UserMapper是一个接口,里面有很多方法,都是一一和配置文件里面的sql语句的id名称所对…...
实体对象说明
1.工具类层Utilutil 工具顾明思义,util层就是存放工具类的地方,对于一些独立性很高的小功能,或重复性很高的代码片段,可以提取出来放到Util层中。2.数据层POJO对象(概念比较大) 包含了以下POJO plain ord…...

JAVA中加密与解密
BASE64加密/解密 Base64 编码会将字符串编码得到一个含有 A-Za-z0-9/ 的字符串。标准的 Base64 并不适合直接放在URL里传输,因为URL编码器会把标准 Base64 中的“/”和“”字符变为形如 “%XX” 的形式,而这些 “%” 号在存入数据库时还需要再进行转换&…...

改进YOLO系列 | ICLR2022 | OMNI-DIMENSIONAL DYNAMIC CONVOLUTION: 全维动态卷积
单个静态卷积核是现代卷积神经网络(CNNs)的常见训练范式。然而,最近的动态卷积研究表明,学习加权为其输入依赖注意力的n个卷积核的线性组合可以显著提高轻量级CNNs的准确性,同时保持高效的推理。然而,我们观察到现有的作品通过卷积核空间的一个维度(关于卷积核数量)赋予…...
信息收集之Github搜索语法
信息收集之Github搜索语法1.Github的搜索语法2.使用 Github 进行邮件配置信息收集3.使用Github进行数据库信息收集4.使用Github进行 SVN 信息收集5.使用Github进行综合信息收集在测试的信息收集阶段,可以去Github和码云上搜索与目标有关的信息,或者就有意…...

【案例教程】拉格朗日粒子扩散模式FLEXPART
拉格朗日粒子扩散模式FLEXPART通过计算点、线、面或体积源释放的大量粒子的轨迹,来描述示踪物在大气中长距离、中尺度的传输、扩散、干湿沉降和辐射衰减等过程。该模式既可以通过时间的前向运算来模拟示踪物由源区向周围的扩散,也可以通过后向运算来确定…...

试题 算法训练 自行车停放
问题描述 有n辆自行车依次来到停车棚,除了第一辆自行车外,每辆自行车都会恰好停放在已经在停车棚里的某辆自行车的左边或右边。(e.g.停车棚里已经有3辆自行车,从左到右编号为:3,5,1。现在编号为2的第4辆自行车要停在5号自行车的左…...

泛型与Map接口
Java学习之道 泛型 泛型这种参数类型可以用在类、方法和接口中,分别被称为泛型类,泛型方法,泛型接口 参数化类型:将类型由原来的具体的类型参数化,在使用/调用时传入具体的类型JDK5引入特性提供了安全检测机制…...
Unity Bug记录本
//个人记录,持续更新 1、将此代码挂载到空脚本上: bool flag (object)GetComponent<Camera>() null; bool flag1 (object)GetComponent<Text>() null; Debug.Log(flag"::"flag1); //输出结果:False::True bool…...
B. The Number of Products)厉害
You are given a sequence a1,a2,…,ana1,a2,…,an consisting of nn non-zero integers (i.e. ai≠0ai≠0). You have to calculate two following values: the number of pairs of indices (l,r)(l,r) (l≤r)(l≤r) such that al⋅al1…ar−1⋅aral⋅al1…ar−1⋅ar is neg…...
一起Talk Android吧(第五百一十二回:自定义Dialog)
文章目录整体思路实现方法第一步第二步第三步第四步各位看官们大家好,上一回中咱们说的例子是"自定义Dialog主题",这一回中咱们说的例子是" 自定义Dialog"。闲话休提,言归正转, 让我们一起Talk Android吧!整体…...

GinVueAdmin源码分析3-整合MySQL
目录文件结构数据库准备配置文件处理config.godb_list.gogorm_mysql.gosystem.go初始化数据库gorm.gogorm_mysql.go开始初始化测试数据库定义实体类 Userserviceapi开始测试!文件结构 本文章将使用到上一节创建的 CommonService 接口,用于测试连接数据库…...
大数据框架之Hadoop:MapReduce(三)MapReduce框架原理——MapReduce开发总结
在编写MapReduce程序时,需要考虑如下几个方面: 1、输入数据接口:InputFormat 默认使用的实现类是:TextInputFormatTextInputFormat的功能逻辑是:一次读一行文本,然后将该行的起始偏移量作为key࿰…...

requests---(4)发送post请求完成登录
前段时间写过一个通过cookies完成登录,今天我们写一篇通过post发送请求完成登录豆瓣网 模拟登录 1、首先找到豆瓣网的登录接口 打开豆瓣网站的登录接口,请求错误的账号密码,通过F12或者抓包工具找到登录接口 通过F12抓包获取到请求登录接口…...

Python抓取数据具体流程
之前看了一段有关爬虫的网课深有启发,于是自己也尝试着如如何过去爬虫百科“python”词条等相关页面的整个过程记录下来,方便后期其他人一起来学习。 抓取策略 确定目标:重要的是先确定需要抓取的网站具体的那些部分,下面实例是…...

【Python学习笔记】第二十四节 Python 正则表达式
一、正则表达式简介正则表达式(regular expression)是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。正则表达式是对字符串(包括普通字符(例如,a 到 z 之间的字母)和特…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...

GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...