【C++】优先级队列宝藏岛

> 🍃 本系列为初阶C++的内容,如果感兴趣,欢迎订阅🚩
> 🎊个人主页:[小编的个人主页])小编的个人主页
> 🎀 🎉欢迎大家点赞👍收藏⭐文章
> ✌️ 🤞 🤟 🤘 🤙 👈 👉 👆 🖕 👇 ☝️ 👍
目录
🐼前言
🐼priority_queue的介绍和使用
🐼仿函数
🐼priority_queue的模拟实现
🐼总结
🐼前言
🌈 在上一节,我们通过适配器模式,实现了一种高效、灵活且易于扩展的方式来实现栈和队列,通过适配器转换出我们想要的类,让原本互不相容的两个接口能够协同工作。本篇文章将继续采用适配器这种设计方式,带你认识优先级队列的使用场景,完成优先级队列(priority_queue)的实现。
🌻如果你还不了解适配器,可以看这一节: stack和queue的适配器模式
🌻如果你还不了解堆,可以看这一节:堆-上和 堆-下
🐼priority_queue的介绍和使用
priority_queue的介绍:
🔍使用场景
优先级队列在我们日常生活中也有很多使用场景,比如在医院的急诊室😷中,患者不是按照到达时间先后顺序接受治疗,而是根据病情的严重程度(优先级)进行排序。病情最严重的患者会被优先处理,即使他们不是最早到达的。又或是在餐厅中,顾客的订单可能根据订单的复杂程度或紧急程度进行优先处理。例如,简单的订单(如一杯咖啡😋)可能会比复杂的订单(如一份牛排)更早完成,即使它们不是最早下单的。这种优先级排序的机制类似于 priority_queue
🔍C++中:优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中优先级最高。我们在容器中可以随时插入元素,它通过动态调整队列顺序,确保优先级最高的元素始终位于队列顶部。
我们下面看一下优先级队列类的类模版参数:
我们可以看到优先级队列有三个模版参数,分别是类型,适配器(缺省参数默认是<vector>),以及仿函数。默认是大堆
我们再看其常见接口:
🔍 priority_queue的使用:
首先,优先级队列并不是队列,底层可以采用堆结构的一种强大的容器。优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,所有需要用到堆的位置,都可以考虑使priority_queue。注意:默认情况下priority_queue是大堆。
比如:我们采用优先级队列来建堆并访问堆顶元素:
#include<iostream> #include <vector> #include <queue>using namespace std;void TestPriorityQueue() {// 默认情况下,创建的是大堆,其底层按照小于号比较vector<int> v{ 3,2,7,6,0,4,1,9,8,5 };priority_queue<int> q1;for (auto& e : v)q1.push(e);cout << q1.top() << endl;// 如果要创建小堆,将第三个模板参数换成greater比较方式priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());cout << q2.top() << endl; }⭐️我们也可以拿自定义类型进行建堆,只要我们提供了自定义类型的比较(自定义类型的比较可以通过运算符重载).
比如以日期类的大小关系来建堆:
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); }friend ostream& operator<<(ostream& _cout, const Date& d) {_cout << d._year << "-" << d._month << "-" << d._day;return _cout; }private:int _year;int _month;int _day;};void TestPriorityQueue(){// 大堆,需要用户在自定义类型中提供<的重载priority_queue<Date> q1;q1.push(Date(2018, 10, 29));q1.push(Date(2018, 10, 28));q1.push(Date(2018, 10, 30));cout << q1.top() << endl;// 如果要创建小堆,需要用户提供>的重载priority_queue<Date, vector<Date>, greater<Date>> q2;q2.push(Date(2018, 10, 29));q2.push(Date(2018, 10, 28));q2.push(Date(2018, 10, 30));cout << q2.top() << endl;}🍁想必看到这里,小伙伴们对优先级队列有了充分的认识。它不就是堆嘛❓❓❓😶其实,priority_queue是堆的具体体现,是一个封装了堆操作的高级工具,而堆是实现这种功能的底层结构。priority_queue是一个更高级的数据结构,封装了堆的操作,提供了更简洁的接口😏。
🐝打个比喻:堆像是一个“有序的箱子”,里面的物品(元素)按照一定的规则(堆序)排列,需要手动调整物品的位置。
priority_queue则像是一个“自动排序的传送带”,你只需要把物品放上去(
push),它会自动按照优先级排序,并且总是让你先拿到最重要的物品(top和pop)。
🐼仿函数
🍁在实现优先级队列之前,我们先简单认识一下仿函数,后续我们在学习。在 C++ 中, 仿函数(Functor) 是一种特殊的函数对象,它通过 重载operator() ,使得对象可以像函数一样被调用。仿函数本质上是一个类,但它的行为类似于函数。比如:用仿函数来实现加法逻辑:class Adder { public:int operator()(int a, int b) const {return a + b;} };int main() {Adder add;int result = add(3, 4); // 使用仿函数对象调用std::cout << result << std::endl; return 0; }📆通过类Adder中重载operator(),add看上去好像是函数调用一样,像一个披着函数外衣的对象。
通过这种方式,对象可以像普通函数一样被调用,但同时可以利用类的其他特性(如成员变量、继承等)。通过重载operator()实现的函数对象,它结合了类的灵活性和函数的简洁性。仿函数广泛应用于 C++ STL 中,用于自定义算法的行为。
🐼priority_queue的模拟实现
基于堆的思想,我们先简单实现一个能完成大堆的priority_queue
namespace lsg {template<class T, class Container = vector<T>>class priority_queue{public:void push(const T& x){_con.push_back(x);Adjustup(size() - 1);}void pop(){std::swap(_con[0], _con.back());_con.pop_back();Adjustdown(0);}bool empty(){return _con.empty();}const T& top() const{return _con[0];}size_t size() const{return _con.size();}private:void Adjustup(size_t child){size_t parent = (child - 1) / 2;while (child > 0){if (_con[parent]< _con[child]) //相当于调用com.opeartor(x,y){std::swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void Adjustdown(size_t parent){size_t child = parent * 2 + 1;while (child < size()){//大堆找较大的孩子if (child + 1 < size() && _con[child]< _con[child + 1]){child++;}if (_con[parent]<_con[child]){std::swap(_con[parent], _con[child]);parent = child;child = parent * 2 + 1;}else{break;}}}Container _con;}; }✅代码解析:
🏃我们这里priority_queue的底层结构就是堆,因此此处只需对堆进行通用的封装即可,堆结构底层是<vector>加以限制,因此可以借助<vector>作为适配器。基于向上调整算法和向下调整算法,让优先级队列永远保持优先级最高的在堆顶。
👆向上调整算法:对于刚刚插入一个小伙伴,它不知道在这个队伍中的"重要程度",因此,它要不断向上查找,直到找到合适自已的位置。
👇向下调整算法:对于把最后一个元素交换到优先级最高位置(第一个位置)的小伙伴,他要不断向下比较,和比自已"重要程度"高的人交换位置,直到找到合适自已的位置。具体操作:先交换堆顶元素与堆中最后一个元素,然后删去最后一个位置的元素,最后向下调整保持堆结构。
👏细节:这里向上调整算法和向下调整算法只供类中成员函数使用,外界无需使用,我们可以封装成(private)权限😃
💅如果我们再实现一个小堆逻辑的priority_queue,只需要更换一下比较逻辑。如果我们再重新写一个小堆逻辑的priority_queue,显得有点繁琐了。在上面我们介绍了仿函数,仿函数就是来用于和容器结合,实现逻辑变换的。而我们这里恰好需要一个相反的大于&&小于逻辑,于是我们这里就可以利用仿函数来控制比较逻辑😃, 定义元素之间的比较逻辑,从而灵活地改变优先队列的行为。
C++标准库中<less>类对象默认是大堆逻辑
namespace lsg {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 = less<T>>//less建大堆class priority_queue{public:void push(const T& x){_con.push_back(x);Adjustup(size()-1);}void pop(){std::swap(_con[0], _con.back());_con.pop_back();Adjustdown(0);}bool empty(){return _con.empty();}const T& top() const{return _con[0];}size_t size() const{return _con.size();}private:void Adjustup(size_t child){Compare com;size_t parent = (child - 1) / 2;while (child > 0){if (com(_con[parent],_con[child])) //相当于调用com.opeartor(x,y){std::swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void Adjustdown(size_t parent){Compare com;size_t child = parent * 2 + 1;while (child<size()) {//大堆找较大的孩子if (child + 1 < size() && com(_con[child] , _con[child + 1])){child++;}if (com(_con[parent] , _con[child])){std::swap(_con[parent], _con[child]);parent = child;child = parent * 2 + 1;}else{break;}}}Container _con;}; }通过自定义比较逻辑,我们可以轻松地实现小堆或大堆,而无需重新编写底层逻辑😁。
🐼总结
优先级队列就像一个“魔法盒”🎃,当一个个元素进去,它总是会给这些元素的重要程度排个序,优先级最高的往往占据首位,优先级最低的只能乖乖让位置。通过仿函数,它就像一个"智能控制管家",让我们控制了这个"比较规则",就好比,如果刚开始身高最高的人优先级最高,通过仿函数的控制,我们也可以让最矮的人优先级最高😜。
感谢你耐心地阅读到这里,你的支持是我不断前行的最大动力。如果你觉得这篇文章对你有所启发,哪怕只是一点点,那就请不吝点赞👍,收藏⭐️,关注🚩吧!你的每一个点赞都是对我最大的鼓励,每一次收藏都是对我努力的认可,每一次关注都是对我持续创作的鞭策。希望我的文字能为你带来更多的价值,也希望我们能在这个充满知识与灵感的旅程中,共同成长,一起进步。再次感谢你的陪伴,期待与你在未来的文章中再次相遇!⛅️🌈 ☀️
相关文章:
【C++】优先级队列宝藏岛
> 🍃 本系列为初阶C的内容,如果感兴趣,欢迎订阅🚩 > 🎊个人主页:[小编的个人主页])小编的个人主页 > 🎀 🎉欢迎大家点赞👍收藏⭐文章 > ✌️ 🤞 …...
开关电源实战(一)宽范围DC降压模块MP4560
系列文章目录 文章目录 系列文章目录MP4560MP4560 3.8V 至 55V 的宽输入范围可满足各种降压应用 MOSFET只有250mΩ 输出可调0.8V-52V SW:需要低VF肖特基二极管接地,而且要靠近引脚,高压侧开关的输出。 EN:输入使能,拉低到阈值以下关闭芯片,拉高或浮空启动 COMP:Compens…...
Git是什么
简单介绍: Git是一个分布式版本控制系统,用于跟踪文件的更改,特别是在多人协作开发的环境中。 Key: 分布式 版本控制 系统 最常用于软件开发,但也可以用于管理任何类型的文件和文件夹。 Git帮助团队跟踪和管理文件的历史版本&a…...
双非计科毕业,二战未果想就业,选择嵌入式开发还是Java开发更合适?
今天给大家分享的是一位粉丝的提问,双非计科毕业,二战未果想就业,选择嵌入式开发还是Java开发更合适? 接下来把粉丝的具体提问和我的回复分享给大家,希望也能给一些类似情况的小伙伴一些启发和帮助。 同学提问&#x…...
性格测评小程序开发指南
目录 前言目录01 需求分析02 数据源设计03 搭建用户管理04 题库管理05 用户注册06 用户注册校验07 用户登录08 测评功能搭建09 提交结果10 生成报告 学习目标面向人群结语 前言 欢迎阅读《性格测评小程序开发指南》!本书旨在为开发者、低代码爱好者和学习者提供一个…...
shell编程总结
前言 shell编程学习总结,1万3千多字带你学习shell编程 往期推荐 14wpoc,nuclei全家桶:nuclei模版管理工具Nuclei 哥斯拉二开,免杀绕过规避流量检测设备 fscan全家桶:FscanPlus,fs,fscan适用…...
析言GBI:用自然语言交互重构企业数据分析范式
亲爱的小伙伴们😘,在求知的漫漫旅途中,若你对深度学习的奥秘、Java 与 Python 的奇妙世界,亦或是读研论文的撰写攻略有所探寻🧐,那不妨给我一个小小的关注吧🥰。我会精心筹备,在未来…...
【论文技巧】Mermaid VSCode插件制作流程图保存方法
插流程图快点 利用Mermaid Preview插件自带功能 如果你的VSCode安装了支持导出图片的Mermaid预览插件(如 Mermaid Markdown Syntax Highlighting 等),可以按以下步骤进行: 打开Mermaid代码文件:在VSCode中打开包含M…...
Unity 位图字体
下载Bitmap Font Generator BMFont - AngelCode.com 解压后不用安装直接双击使用 提前设置 1、设置Bit depth为32 Options->Export options 2、清空所选字符 因为我们将在后边导入需要的字符。 Edit->Select all chars 先选择所有字符 Edit->Clear all chars i…...
科技快讯 | DeepSeek推出NSA加速长上下文训练,xAI Grok系列将陆续开源,月之暗面发布Kimi Latest新模型
阶跃星辰首次开源Step系列多模态大模型 2月18日,财联社消息,阶跃星辰与吉利汽车集团宣布,双方合作开发的阶跃Step系列多模态大模型向全球开发者开源。包括参数量达300亿的Step-Video-T2V视频生成模型和行业内首款产品级开源语音交互大模型Ste…...
网络安全 | 5G网络安全:未来无线通信的风险与对策
网络安全 | 5G网络安全:未来无线通信的风险与对策 一、前言二、5G 网络的技术特点2.1 超高速率与低延迟2.2 大容量连接与网络切片三、5G 网络面临的安全风险3.1 网络架构安全风险3.2 设备终端安全风险3.3 应用场景安全风险3.4 用户隐私安全风险四、5G 网络安全对策4.1 强化网络…...
Linux 实操篇 组管理和权限管理、定时任务调度、Linux磁盘分区和挂载
一、组管理和权限管理 (1)Linux组基本介绍 在linux中的每个用户必须属于一个组,不能独立于组外 在linux中每个文件有所有者、所在组、其他组的概念 (2)文件/目录 所有者 一般为文件的创建者,谁创建了该…...
应用案例 | uaGate SI助力汽车零部件工厂将生产数据传输到MES
一、背景和挑战 (图1 汽车零部件工厂生产车间) 随着汽车工业的不断发展,新能源汽车市场的竞争日益激烈,这对汽车零部件供应商提出了更高的要求,包括提升产品精度、增强可靠性、节能环保以及控制成本等多个方面。某国际…...
Android JNI的理解与使用。
写在前面:Java相对于C/C来说是更高级的语言,隐藏了指针,可读性更高,更容易学习,但是无法直接操作硬件、运行速度较慢也是不可回避的硬伤。JNI就是Java官方定义的一套标准“接口”,用于Java和C/C之间互相调用…...
fpga助教面试题
第一题 module sfp_pwm( input wire clk, //clk is 200M input wire rst_n, input wire clk_10M_i, input wire PPS_i, output reg pwm ) reg [6:0] cunt ;always (posedge clk ) beginif(!rst_n)cunt<0;else if(cunt19) //200M是10M的20倍cunt<0;elsecunt<cunt1;…...
Git命令详解与工作流介绍:全面掌握版本控制系统的操作指南
Git Git是一个版本控制系统(也称为源代码控制系统),允许程序员和其他处理文本文件的人在独立工作时协调更改。Git还支持二进制资产,如图片,但这些格式不支持逐行版本管理,这使得版本控制真正强大。 Git概…...
提升信息检索准确性和效率的搜索技巧
一、基础技巧 精准关键词 避免长句子,提取核心关键词(如用“光合作用 步骤”代替“请告诉我光合作用的具体过程”)。 同义词替换:尝试不同表达(如“AI 发展史” vs “人工智能 历史”)。 排除干扰词 使用…...
Qt 中使用 ffmpeg 获取采集卡数据录制视频
作者:billy 版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处 前言 之前做了一个功能,从采集卡获取数据然后录制成视频,结果发现录制的视频内存占用非常大,1分钟的…...
Python爬虫TLS
TLS指纹校验原理和绕过 浏览器可以正常访问,但是用requests发送请求失败。 后端是如何监测得呢?为什么浏览器可以返回结果,而requests模块不行呢? https://cn.investing.com/equities/amazon-com-inc-historical-data 1.指纹校…...
【Linux AnolisOS】配置Linux固定ip地址。然后在Windows上连接使用linux中docker容器里的redis和nacos。
1.关于将虚拟机ip地址更改为静态地址 ,跟着下面这个视频搞的,不想看文章的可以看视频。 第四章-07-配置Linux固定IP地址哔哩哔哩bilibili 当用的centos9 视频里让我们打开网络配置文件 vim /etc/sysconfig/network-scripts/ifcfg-ens33 但是我打开时…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...

