初识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…...
爬山算法的详细介绍
爬山算法(Hill Climbing Algorithm)是一种基于启发式的局部搜索算法,常用于解决优化问题。它的核心思想是从当前解的邻域中选择能够使目标函数值最大(或最小)的下一个解作为当前解,直到找到一个满足问题要求…...
硕士课程 可穿戴设备之作业一
作业一 第一个代码使用的方法是出自于[1]。 框架结构 如下图,不过根据对代码的解读,发现作者在代码中省去了对SSR部件的实现,下文再说。 Troika框架由三个关键部件组成:信号分解,SSR和光谱峰值跟踪。(粗…...
测试记录3:WLS2运行Linux界面
1.WLS1转到WLS2 (1)根据自己的平台,下载WLS2安装包 x64: https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi arm64: https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_arm64.msi (2&…...
好用软件推荐
软件功能相关介绍地址FastStone截图(长截图、定时截图等)CSDNhttps://www.faststone.org/FSCaptureDownload.htmQuicker快捷访问https://getquicker.net/https://getquicker.net/...
王学岗鸿蒙开发(北向)——————(二)TS基本语法详解
1,Ts(TypeScript)语法相当于JAVAScript类型,鸿蒙arkTs是基于TS语言的,当然artTs也融合了其它的语言。 2,本篇文章是基于n9版本。注意,有些语法是已经不能用的。 3, 4,变量:用来存储数据,数字字母组成,数字不…...
【网络协议 | HTTP】HTTP总结与全梳理(一) —— HTTP协议超详细教程
🔥博客简介:开了几个专栏,针对 Linux 和 rtos 系统,嵌入式开发和音视频开发,结合多年工作经验,跟大家分享交流嵌入式软硬件技术、音视频技术的干货。 ✍️系列专栏:C/C、Linux、rtos、嵌入式…...
java基础选择题--11
1. 以下保留字( )不能出现在说明虚函数原型的语句中。A.static B.operator C.void D.const 参考答案:A 2. 一个类中只能定义一个析构函数。( )A.对 B.错 参考答案:A 解释: 在C中,一个类只能有一个析构函数。析构函数在对象生…...
欲除烦恼须无我,各有前因莫羡人
欲除烦恼须无我,各有前因莫羡人...
Vue的APP实现下载文件功能,并将文件保存到手机中
Vue的APP实现下载文件功能,并将文件保存到手机中 文字说明后台核心代码前台核心代码运行截图项目链接 文字说明 本文介绍Vue实现的APP,将文件下载并保存到手机中,为系统提供导出功能;同时支持导入,即选择本地的文件后&…...
泛微开发修炼之旅--07通过后端代码实现创建并发送待办、源码及示例
文章链接:泛微开发修炼之旅--07通过后端代码实现创建并发送待办、源码及示例...
荆州哪里有做网站的/百度投广告怎么收费
ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类; JDK api里是这么说的: ThreadPoolExecutor,它可另行安排在给定的延迟后运行命令,或者定期执行命令。需要多个辅助线程时,或者要求 ThreadPoolExecutor 具有额…...
汕头模版网站建设/青岛做网站推广公司
Threading&MultiProcessing1.多线程什么是多线程线程的添加join功能QueueGIL线程安全与Lock锁2.多进程什么是多进程创建多进程queue进程池PoolShare Memory与Lock锁3.多线程与多进程的选择1.多线程 什么是多线程 线程是操作系统能够进行运算调度的最小单位;它…...
网站空间信息查询/营销策划方案案例
老实讲,我也是昨天才看到PW具有校审流程功能,这么好的功能为何我们单位不用呢?一个字,省略,2个字,省略,这个文档讲的最清楚,一二三四几步搞定流程设置,然后就是走流程了。…...
免费域名解析网站建设/网络营销什么意思
有两种方法: 手动安装,需要自己去Oracle官网下载需要的JDK版本,然后解压并配置环境;yum安装 注意:场景不同可能会需要不同类型的JDK,因为Open JDK和Orcale JDK是有区别的,不过安装和配置的步骤…...
可以做翻译的网站/零食软文范例300字
WebStorm智能 JavaScript IDE WebStorm 是一个现代 JavaScript 生态系统。它包括智能代码完成、动态错误检测、强大的导航和 JavaScript、TypeScript、样式表语言和所有流行框架的重构。 WebStorm 功能 智能编码辅助- WebStorm 为您提供 JavaScript 和编译成 JavaScript 语言、…...
专业做电子的外贸网站/网站建设及网站推广
第十章:Call指令和Ret指令讲解03 让编程改变世界 Change the world by program call指令和ret指令的配合使用2 我们看一下程序的主要执行过程: (1)前三条指令执行后,栈的情况如下: [caption id"at…...