初识C++ · 优先级队列
目录
前言:
1 优先级队列的使用
2 优先级队列的实现
3 仿函数
前言:
栈和队列相对其他容器来说是比较简单的,在stl里面,有一种容器适配器是优先级队列(priority_queue),它也是个队列,但是不大像队列,本文中简略介绍如何使用和模拟实现它。
1 优先级队列的使用
要使用,先文档:

文档黑体第一句话就是,优先级队列是一种容器配置器,容器配置器是?电脑有电源适配器,起到提供电源的作用,但是这个适配器不是充电器,即充电的时候,电源来自于电线,但是我们不能直接拿电线充电吧?这时候适配器就有用了,通过封装,使得两边达到一个配合的效果,容器配置器同理。当然这不是重点。
模板是必要的,模板参数有3个,参考栈和队列,第二个参数是底层用的哪种容器进行实现的,这里的默认是使用的顺序表,第三个参数是仿函数,后面介绍。
那么在来看看成员函数:

很少是吧,可以说成员函数和队列没有什么差别,所以我们现在简单使用一下:
int main()
{priority_queue<int> q1;q1.push(2);q1.push(1);q1.push(4);q1.push(3);q1.push(5);cout << q1.size() << endl;while (!q1.empty()){cout << q1.top() << " ";q1.pop();}cout << endl;cout << q1.size() << endl;return 0;
}

使用很简单,但是打印的结果却,,有点奇怪?
这就是优先级队列的特殊之处了,我们并没有对它进行排序,但是打印出来是默认有序的,这是因为它的本质是堆,而模板参数第三个仿函数的参与,决定了它是大堆还是小堆,默认是升序的,可以理解为升序状态下谁最小谁优先级最高,所以先被打印。
但是实际上,不用管那么多,就把它认为是堆就ok了,所以我们实现的时候需要实现向上调整算法和向下调整算法。
对于仿函数我们放在实现了80%在介绍。
2 优先级队列的实现
我们需要实现以下几个接口:
empty,size,push,pop,top,接口不多,另外还要实现向上调整算法和向下调整算法。
优先级队列的一般结构为:
template <class T,class container = vector<T>>
class priority_queue
{
public:private:container _con;};
模板的第三个参数先不急。
简单的几个接口我们一股脑实现就完事了:
void push(const T& val)
{_con.push_back(val);adjust_up(_con.size() - 1);
}bool empty()
{return _con.empty();
}void pop()
{swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);
}size_t size()
{return _con.size();
}const T& top()
{return _con[0];
}
这部分是没什么难度的。
随机要实现的是push和pop,这里提问,为什么默认的底层实现是vector呢?
因为堆的本质,就是一块连续的空间,我们只是把它抽象为了完全二叉树。
push数据之后,堆本身的结构被破坏了,所以我们需要向上调整数据,在数据结构初级的时候,已经介绍过两种算法,这里过一下就Ok:
向上调整算法是通过孩子节点和父节点的值进行比较,然后交换位置,可能有点倒反天罡,谁的值大谁就当爸爸?我们知道孩子节点的下标之后,父节点的下标就是(孩子下标 - 1) / 2,如果不熟悉可以拿图推演一下,最后一次交换的时候,孩子节点的下标变成了父节点的下标,所以判断条件应该是孩子节点的下标是否合法,即是否大于0:
void adjust_up(size_t child)
{size_t parent = (child - 1) / 2;while (child > 0){if(_con[parent] < _con[child]){swap(_con[parent],_con[child]);child = parent;parent = (child - 1) / 2;}else{break;}}
}
此时push的的准备工作就做好了,插入,调整,完成:
void push(const T& val)
{_con.push_back(val);adjust_up(_con.size() - 1);
}
与之对应的是pop,pop对应的准备工作是向下调整算法,向下调整算法有几个需要注意的点就是,我们建大堆,那么就要在孩子节点中找到两个孩子中较大的那一个,除此之后,不是所有的节点都有两个子节点,所以我们需要保证孩子 + 1不小于size,这是基础工作,随机就是比较大小,谁大谁就往上:
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 = parent * 2 + 1;}else{break;}}
pop的准备工作也是完毕了,那么pop就行,对于堆部分有些遗忘的同学就会想为什么涉及到向下调整,因为删除,为了效率和不破坏堆的结构,我们的措施是首尾交换,向下调整。
void pop()
{swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);
}
3 仿函数
仿函数是本文的重点,我们需要先知道什么是仿函数?
先看一段这样的代码:
template <class T>
struct Less
{bool operator()(const T& a,const T& b){return a > b;}
};
void Func(int ,int)
{cout << endl;
}int main()
{Less<int> Fun;Fun(1,2);Func(1,2);return 0;
}
如果说只看主函数的代码,就会容易觉得,这不就是两个函数调用吗?仿函数仿函数,顾名思义,模仿函数,因为调用的时候挺像函数的,但是实际上,它是一个类,这个类实例化对象,使用重载后的(),我们称这个过程叫做仿函数调用,仿函数实际上只有一个东西,就是重载的()。
重载的()没有其他东西,只有比较,对于内置类型我们可以直接比较,对于自定义类型我们可能要重载一下> 或者 <。
所以,仿函数是个类,类里面的函数是重载的(),一般是两个参数,用于比较使用。
所以我们在向上调整算法和向下调整算法的实现里面的比较,就可以用仿函数完成,进而控制升序降序,那么我们就还要写两个类:
//仿函数
template <class T>
struct less
{bool operator()(const T& x,const T& y){return x < y;}
};template <class T>
struct greater
{bool operator()(const T& x, const T& y){return x > y;}
};
那么对应更新的,就是我们的两个算法:
template <class T,class container = vector<T>,class compare = greater<T>>void adjust_up(size_t child)
{compare com;size_t parent = (child - 1) / 2;while (child > 0){//if (_con[parent] < _con[child])if(com(_con[parent] , _con[child])){swap(_con[parent],_con[child]);child = parent;parent = (child - 1) / 2;}else{break;}}
}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 (_con[child] > _con[parent])if(com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}
向下调整算法这里有个注意的,if(child + 1 < _con.size() && com(_con[child], _con[child + 1])),第一个判断条件放在前面是最好的,因为越界了不用了比较了,代码逻辑更严密。
但是这里使用了仿函数好像也没有什么特别的,无非就是用了一个像函数调用一样的类而已,为什么不用C语言中qsort里面的函数指针呢?
在C++里面函数指针并不常用,难写不说,代码的可移植性还不高,目前我们针对的内置类型使用的仿函数效果不明显,我们引入一个日期类,一个自定义类型,进行日期的比较。
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}
private:size_t _year;size_t _month;size_t _day;
};
对于自定义类型的比较重载已经写在了类里面,现在直接比较就可以了:
class DateGreater
{bool operator()(const Date& d1, const Date& d2){return d1 > d2;}
};
class DateLess
{bool operator()(const Date& d1, const Date& d2){return d1 < d2;}
};
void Test2_priority_queue()
{priority_queue<Date> q1;q1.push({ 2024, 4, 17 });q1.push({ 2024, 4, 18 });q1.push({ 2024, 4, 19 });q1.push({ 2024, 4, 20 });cout << q1.size() << endl;while (!q1.empty()){cout << q1.top() << " ";q1.pop();}cout << endl;
}
对比淘宝,淘宝的的排序,比如综合评分排序,销量排序等,用户点击对应按钮,系统就会调用对应的函数,使用的就是仿函数,函数指针在C++里面不太常用,它的类型和变量名混杂一起,看起来不太舒服。
仿函数的一般使用差不多了,但是如果我们给优先级队列里面存放日期类的指针,但是相比较日期类的大小怎么办呢?
void Test3_priority_queue()
{priority_queue<Date*, vector<Date*>> q1;q1.push(new Date(2024,5,20));q1.push(new Date(2024,5,21));q1.push(new Date(2024,5,22));q1.push(new Date(2024,5,23));while (!q1.empty()){cout << *(q1.top()) << " ";q1.pop();}cout << endl;
}
比如这样,里面都是指针,我们push的也是new出来的指针,我们使用的仿函数是:
class DateGreater
{bool operator()(const Date& d1, const Date& d2){return d1 > d2;}
};
class DateLess
{bool operator()(const Date& d1, const Date& d2){return d1 < d2;}
};
那么这个仿函数就不可以了,比较出来的都是随机结果,这是因为new出来的地址都是随机的,而默认的是比较的指针,打印出来的是对应的日期,所以结果一会儿是对的一会儿是错的,这是因为仿函数的错误使用,我们应该解引用之后再进行比较,这里就要另外提供一个仿函数了:
class PDateGreater
{
public:bool operator()(Date* d1, Date* d2){return *d1 > *d2;}
};
光提供了还不行,我们还要显式调用:
void Test3_priority_queue()
{priority_queue<Date*, vector<Date*>,PDateGreater> q1;q1.push(new Date(2024,5,20));q1.push(new Date(2024,5,21));q1.push(new Date(2024,5,22));q1.push(new Date(2024,5,23));while (!q1.empty()){cout << *(q1.top()) << " ";q1.pop();}cout << endl;
}
这样就是完美的了。仿函数的简单介绍就到这里。
感谢阅读!
相关文章:
初识C++ · 优先级队列
目录 前言: 1 优先级队列的使用 2 优先级队列的实现 3 仿函数 前言: 栈和队列相对其他容器来说是比较简单的,在stl里面,有一种容器适配器是优先级队列(priority_queue),它也是个队列&#…...
php反序列化入门
一,php面向对象。 1.面向对象: 以“对象”伪中心的编程思想,把要解决的问题分解成对象,简单理解为套用模版,注重结果。 2.面向过程: 以“整体事件”为中心的编程思想,把解决问题的步骤分析出…...
嵌入式 Linux LED 驱动开发实验学习
I.MX6U-ALPHA 开发板上的 LED 连接到 I.MX6ULL 的 GPIO1_IO03 这个引脚上,进行这个驱动开发实验之前,需要了解下地址映射。 地址映射 MMU 全称叫做 MemoryManage Unit,也就是内存管理单元。在老版本的 Linux 中要求处理器必须有 MMU&#x…...
C++:多态
文章目录 多态的概念多态的定义及实现多态的构成条件虚函数虚函数的重写override 和 final重载、重写(覆盖)、重定义(隐藏)的对比 抽象类概念接口继承和实现继承 多态的原理虚函数表多态的原理 单继承和多继承关系的虚函数表单继承…...
Java事务入门:从基础概念到初步实践
Java事务入门:从基础概念到初步实践 引言1. Java事务基础概念1.1 什么是事务?1.2 为什么需要事务? 2. Java事务管理2.1 JDBC 的事务管理2.2 Spring 事务管理2.2.1 Spring JDBC2.2.1.1 添加 Spring 配置2.2.1.2 添加业务代码并测试验证 2.2.2…...
鸿蒙轻内核M核源码分析系列七 动态内存Dynamic Memory
内存管理模块管理系统的内存资源,它是操作系统的核心模块之一,主要包括内存的初始化、分配以及释放。 在系统运行过程中,内存管理模块通过对内存的申请/释放来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优&#x…...
从头搭hadoop集群--分布式hadoop集群搭建
模板虚拟机安装配置见博文:https://blog.csdn.net/weixin_66158110/article/details/139236148 配置文件信息如下:https://pan.baidu.com/s/1074eD5aNVugEPcjwVvi9jA?pwdl1xq(提取码:l1xq) hadoop版本:h…...
odoo10 权限控制用户只允许看到自己的字段
假设一个小区管理员用户,只想看到自己小区的信息。 首先添加一个用户信息选项卡界面,如下图的 用户 > 隶属信息: 我们在自己创建的user模块中,views文件夹下添加base_user.xml <?xml version"1.0" encoding&q…...
图解Mysql索引原理
概述 是什么 索引像是一本书的目录列表,能根据目录快速的找到具体的书本内容,也就是加快了数据库的查询速度索引本质是一个数据结构索引是在存储引擎层,而不是服务器层实现的,所以,并没有统一的索引标准,…...
Arduino网页服务器:如何将Arduino开发板用作Web服务器
大家好,我是咕噜铁蛋!今天,我将和大家分享一个有趣且实用的项目——如何使用Arduino开发板搭建一个简易的网页服务器。通过这个项目,你可以将Arduino连接到互联网,并通过网页控制或查询Arduino的状态。 一、项目背景与…...
大模型日报2024-06-05
大模型日报 2024-06-05 大模型资讯 AI气象预测取得重大进展:单台桌面电脑即可运行全球天气模型 摘要: 一项新的人工智能天气预测模型已经取得重大进展,该模型能够在一台普通的桌面电脑上运行,预测全球天气。这意味着即使没有复杂的物理计算&a…...
LLM 大模型学习必知必会系列(二):提示词工程-Prompt Engineering 以及实战闯关
角色扮演:在系统指令中告诉千问你需要它扮演的角色,即可沉浸式和该角色对话交流语言风格:简单调整 LLM 的语言风格任务设定:比如旅行规划,小红书文案助手这样的专项任务处理System message 也可以被用于规定 LLM 的答复…...
Spring系统学习 - Spring入门
什么是Spring? Spring翻译过来就是春天的意思,字面意思,冠以Spring的意思就是想表示使用这个框架,代表程序员的春天来了,实际上就是让开发更加简单方便,实际上Spring确实做到了。 官网地址:ht…...
Priority_queue
一、priority_queue的介绍和使用 1.1 priority_queue的介绍 1.优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。 2.优先队列类似于堆, 在堆中可以随时插入元素, 并且只能检索最大堆…...
SpringMVC:获取请求数据
1. 通过RequestParma注解接收 /**** value和name都可以使用,互为别名* 如果此处设置了需要什么参数而前端请求时没有提供则会报400(请求参数不一致错误)* required参数用于设置该参数是否为必须传递参数,默认为true必须传递* defa…...
深度学习 --- stanford cs231 编程作业(assignment1,Q2: SVM分类器)
stanford cs231 编程作业之SVM分类器 写在最前面: 深度学习,或者是广义上的任何学习,都是“行千里路”胜过“读万卷书”的学识。这两天光是学了斯坦福cs231n的一些基础理论,越往后学越觉得没什么。但听的云里雾里的地方也越来越多…...
【scikit-learn010】sklearn算法模型清单实战及经验总结(已更新)
1.一直以来想写下基于scikit-learn训练AI算法的系列文章,作为较火的机器学习框架,也是日常项目开发中常用的一款工具,最近刚好挤时间梳理、总结下这块儿的知识体系。 2.熟悉、梳理、总结下scikit-learn框架模型算法包相关技术点及经验。 3.欢迎批评指正,欢迎互三,跪谢一键…...
Rethinking overlooked aspects in vision-language models
探讨多模态视觉语言模型的一些有趣结论欢迎关注 CVHub!https://mp.weixin.qq.com/s/zouNu-g-33_7JoX3Uscxtw1.Introduction 多模态模型架构上的变化不大,数据的差距比较大,输入分辨率和输入llm的视觉token大小是比较关键的,适配器,VIT和语言模型则不是那么关键。InternVL-…...
【漯河市人才交流中心_登录安全分析报告-Ajax泄漏滑动距离导致安全隐患】
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞…...
C语言—字符函数和字符串函数
1.字符分类函数 C语言中有一系列的函数是专门做字符分类的,也就是一个字符是属于什么类型的字符的。 这些函数的使用都需要包含一个头文件 ctype.h。 例:将一句话中的小写字母改成大写字母。 2.字符转换函数 头文件:ctype.h C语言提供了2…...
利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
