【C++】深度解析:用 C++ 模拟实现 priority_queue类,探索其底层实现细节(仿函数、容器适配器)

目录

⭐前言

✨堆

✨容器适配器

✨仿函数

⭐priority_queue介绍 

⭐priority_queue参数介绍 

⭐priority_queue使用

⭐priority_queue实现

✨仿函数实现

✨堆的向上调整和向下调整 

✨完整代码 


⭐前言

✨堆

堆是一种特殊的树形数据结构,通常以二叉树的形式实现,具有特定的排序特性。堆分为两种类型:最大堆和最小堆。 

具体可以参见这篇文章:【数据结构】二叉树-CSDN博客

✨容器适配器

适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总 结),该种模式是将一个类的接口转换成客户希望的另外一个接口。

STL标准库中stack和queue的底层结构:

虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque,比如:

✨仿函数

在 C++ 中,仿函数通常指的是一种行为类似于函数的对象,即可以像调用函数那样被调用的对象。这种对象通常包含一些数据成员,并且重载了括号运算符 operator(),从而允许以函数的方式调用。

看下面这段代码:

class Less
{
public:bool operator()(const int& x, const int& y){return x < y;}
};int main()
{Less lesscom;cout << lesscom(1, 2) << endl;return 0;
}

代码中定义了Less类,重载(),函数中定义了x、y两个参数,当x小于y就返回true,否则返回false。

        在main函数中创建了Less类的对象,如果想要调用重载(),常规的调用方法应该是对象名.函数名(参数列表)。但因为重载()函数是可以省略.operator(),所以当我们使用这个仿函数对象的时候,使用的方法就和调用一个函数一样,这就是仿函数的使用。

仿函数的特点

  1. 可调用性:仿函数通过重载括号运算符 operator() 实现了可调用性,使得我们可以像调用普通函数一样调用仿函数对象。
  2. 状态:仿函数可以拥有自己的数据成员(状态),这意味着每次调用仿函数时,它可以访问这些成员变量,这与普通的函数不同,后者通常不保留状态。
  3. 多态性:由于仿函数是对象,它们可以被用作多态的一部分,这意味着你可以通过基类指针或引用调用派生类的仿函数对象。

仿函数的用途

  • 算法参数化:仿函数可以作为算法的参数,使得算法可以根据传入的不同仿函数表现出不同的行为。
  • 事件处理:在 GUI 编程中,可以使用仿函数作为事件处理器,当事件发生时调用相应的仿函数对象。
  • 模板编程:在 C++ 模板编程中,仿函数经常被用作模板参数,以实现泛型算法

⭐priority_queue介绍 

priority_queue 是 C++ 标准库中的一个容器适配器,它提供了基于最大堆或最小堆的数据结构来实现优先队列的功能。priority_queue 自动维护元素的排序,使得每次插入或删除操作都能保持堆的性质。

priority_queue 底层将vector作为默认容器,默认情况下为大堆。

⭐priority_queue参数介绍 

template <class T, class Container = vector<T>,class Compare = less<typename Container::value_type> > 
class priority_queue;
  1. T: 这是优先队列中存储的元素类型。例如,如果存储整数,则 T 将是 int

  2. Container: 这是一个可选的模板参数,用来指定底层容器的类型。默认情况下,std::priority_queue 使用 std::vector<T> 作为其底层容器。但是,可以选择任何支持随机访问迭代器的容器类型,例如 std::deque<T>。请注意,底层容器必须支持 push_backpop_back 操作。

  3. Compare: 这也是一个可选的模板参数,用来指定元素之间的比较方式。默认情况下,使用 std::less<typename Container::value_type>,这意味着对于类型 T 的元素,将使用 < 运算符进行比较,创建出一个大堆。如果要创建一个最小堆,则可以使用 std::greater<typename Container::value_type>

我们其实可以发现 priority_queue使用的是容器适配器模式,底层是vector和deque这样支持下标随机访问等操作的容器;并且还是要了仿函数 Compare来控制比较逻辑,使用less<typename Container::value_type>创建出大堆,使用greater<typename Container::value_type> 创建出小堆。

priority_queue的一个完整的声明如下:

priority_queue<int, std::vector<int>, std::greater<int>> minHeap;

⭐priority_queue使用

优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成 堆 的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。

注意: 默认情况下priority_queue是大堆。 

函数声明接口说明
priority_queue()/priority_queue(first, last)构造一个空的优先级队列
empty()检测优先级队列是否为空,是返回true,否则返回 false
top()返回优先级队列中最大(最小元素),即堆顶元素
push(x)在优先级队列中插入元素x
pop()删除优先级队列中最大(最小)元素,即堆顶元素

 默认情况下,priority_queue是大堆。

#include <vector>
#include <queue>
#include <functional> // greater算法的头文件
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;
}

 如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的重载。

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实现

✨仿函数实现

仿函数就是它的对象可以想函数一样去使用,本质上是重载了()。

//仿函数//控制大堆
template<class T>
class less
{
public:bool operator()(const T& x, const T&y){return x < y;}
};//控制小堆
template<class T>
class greater
{
public:bool operator()(const T& x, const T& y){return x > y;}
};

 实际上的使用应该像下面图中第二行那样,但是重载之后就省略了。

✨堆的向上调整和向下调整 

大体上的逻辑和堆的实现相同,但是使用仿函数控制比较的逻辑,使得优先队列不仅对基础数据类型,如int,有效,也对想Date这样的日期类型有效(需要重载了>和<)。

//向上调整
void adjust_up(size_t child)
{Compare com;int parent = (child - 1) / 2;while (child > 0){if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);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 (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

✨完整代码 

//仿函数//控制大堆template<class T>class less{public:bool operator()(const T& x, const T&y){return x < y;}};//控制小堆template<class T>class greater{public:bool operator()(const T& x, const T& y){return x > y;}};//less 控制为大堆template<class T,class Container = vector<T>,class Compare = less<T>>class priority_queue{public://向上调整void adjust_up(size_t child){Compare com;int parent = (child - 1) / 2;while (child > 0){//if (_con[child] > _con[parent])//if (_con[parent] < _con[child])if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void push(const T& x){_con.push_back(x);adjust_up(_con.size()-1) ;}//向下调整void adjust_down(size_t parent){Compare com;size_t child = parent * 2 + 1;while (child < _con.size()){//if (child + 1 < _con.size() && _con[child + 1] > _con[child])//if (child + 1 < _con.size() && _con[child] < _con[child + 1])if (child + 1 < _con.size() && com(_con[child], _con[child + 1])){++child;}//if (_con[child] > _con[parent])//if (_con[parent] < _con[child])if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}bool empty(){return _con.empty();}size_t size(){return _con.size();}const T& top(){return _con[0];}private:Container _con;};

____________________

⭐感谢你的阅读,希望本文能够对你有所帮助。如果你喜欢我的内容,记得点赞关注收藏我的博客,我会继续分享更多的内容。⭐

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/52049.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

sql 如何在查询时移除某一行数据以及这一行的上一行数据

oracel 有个rownum 函数, 它可以为 某一行 数据增加一个序列比如查出的序列值便是 [ {"name": "法外狂徒-张三",option":"申请团长", rownum:1}, {"name": "法外狂徒-张三","option":"申请人撤销"…

预训练语言模型PLM(课程笔记)

一&#xff1a;分为两种范式 1. Feature-based 预训练的模型参数&#xff08;word-enbeddings&#xff09;作为下游任务的输入&#xff0c;不更新预训练参数。 代表&#xff1a;word2vec&#xff0c;ELMO 2. Fine-tuning 在下游任务时对模型参数进行更新 代表&#xff1a;BERT&…

笔记本一线品牌有哪些

笔记本电脑的一线品牌通常指的是在市场上具有较高市场份额、良好口碑、较强的技术实力和服务能力的品牌。根据目前的信息&#xff0c;笔记本电脑市场的一线品牌主要包括以下几个&#xff1a; 联想 (Lenovo)&#xff1a;联想在全球笔记本市场上的占有率较高&#xff0c;其产品线…

tomcat日志文件切割

文章目录 引言I 使用用crontab工具,定时执行任务II 通过Linux系统自带的切割工具logrotate来进行切割logrotate 简介用法结合crontab进行自定义的定时轮转操作III 基于其他日志框架进行分隔引言 tomcat 的 catalina.out 文件不会进行日志切割,当这个文件大于2G 时,会影响to…

flume系列之:flume生产环境sink重要参数理解

flume系列之:flume生产环境sink重要参数理解 sink1.hdfs.maxOpenFilessink1.hdfs.threadsPoolSizesink1.hdfs.rollTimerPoolSizesink1.hdfs.rollCountsink1.hdfs.rollSizesink1.hdfs.rollSizesink1.hdfs.rollIntervalsink1.hdfs.idleTimeoutsink1.hdfs.batchSizesink1.hdfs.wr…

PyTorch 基础学习(12)- 自定义运算符

系列文章&#xff1a; 《PyTorch 基础学习》文章索引 介绍 在深度学习的开发中&#xff0c;常常需要为特殊需求定义自定义运算符。PyTorch 提供了 torch.library 这一API集合&#xff0c;允许开发者扩展 PyTorch 核心运算符库&#xff0c;测试自定义运算符&#xff0c;并创建…

C/C++ 多线程[1]---线程创建+线程释放+实例

文章目录 前言1. 多线程创建2. 多线程释放3. 实例总结 前言 说来惭愧&#xff0c;写了很久的代码&#xff0c;一个单线程通全部。可能是接触的项目少吧&#xff0c;很多多线程的概念其实都知道&#xff0c;但是实战并没有用上。前段时间给公司软件做一个进度条&#xff0c;涉及…

[Qt][QSS][下]详细讲解

目录 1.样式属性0.前言1.盒模型(Box Model) 2.常用控件样式属性1.按钮2.复选框3.单选框4.输入框5.列表6.菜单栏7.注意 1.样式属性 0.前言 QSS中的样式属性⾮常多&#xff0c;不需要都记住&#xff0c;核⼼原则是⽤到了就去查 ⼤部分的属性和CSS是⾮常相似的 QSS中有些属性&am…

RK3588——网口实时传输视频

由于通过流媒体服务器传输画面延迟太高的问题&#xff0c;不知道是没有调试到合适的参数还是其他什么问题。诞生了这篇博客。 RK3588板端上接摄像头&#xff0c;采集画面&#xff0c;通过网口实时传输给上位机并显示。 第一代版本 RK3588代码 import cv2 import socket imp…

C++发送邮件:如何稳定实现邮件发送功能?

C发送邮件安全性探讨&#xff01;C编程中发送邮件的技巧&#xff1f; 邮件发送功能是许多应用程序的重要组成部分&#xff0c;无论是用于通知用户&#xff0c;还是用于自动化报告。AokSend将探讨如何在C环境中稳定地实现邮件发送功能&#xff0c;确保邮件能够可靠地到达收件人…

windows环境基于python 实现微信公众号文章推送

材料&#xff1a; 1、python 2.7 或者 python3.x 2、windows 可以通过 “python -m pip --version” 查看当前的pip 版本 E:\Downloads\newsInfo>python -m pip --version pip 20.3.4 from C:\Python27\lib\site-packages\pip (python 2.7) 3、windows 系统 制作&#xf…

云计算实训30——自动化运维(ansible)

自动化运维 ansible----自动化运维工具 特点&#xff1a; 部署简单&#xff0c;使用ssh管理 管理端与被管理端不需要启动服务 配置简单、功能强大&#xff0c;扩展性强 一、ansible环境搭建 准备四台机器 安装步骤 mo服务器&#xff1a; #下载epel [rootmo ~]# yum -y i…

windows主机查询url请求来自哪里发起的

最近使用fiddler抓包&#xff0c;看到一直有http://conna.gj.qq.com:47873 的请求&#xff0c; 对此进行溯源&#xff0c;确定是不是被攻击了。 在dos里查询端口进程&#xff1a;netstat -ano | findstr :47873 查到来自8020的进程id 查看此进程应用&#xff0c;发现竟然是…

C++ 设计模式——外观模式

外观模式 C 设计模式——外观模式主要组成部分1. 外观类&#xff08;Facade&#xff09;2. 子系统类&#xff08;Subsystem&#xff09;3. 客户端&#xff08;Client&#xff09; 例一&#xff1a;工作流程示例1. 外观类&#xff08;Facade&#xff09;2. 子系统类&#xff08;…

IT管理:我与IT的故事6--数字化建设规划工作坊圆满开展

在数字化浪潮席卷全球的时代背景下&#xff0c;企业的数字化转型已成为必然趋势。IT 部落精心打造的数字化规划实操工作坊顺利举办&#xff0c;为众多CIO的数字化转型之路点亮了明灯。 本次工作坊特别邀请到了业界知名的大咖讲师 Frank&#xff0c;他在数字化领域深耕多年&am…

最长的严格递增或递减子数组

给你一个整数数组 nums 。 返回数组 nums 中 严格递增 或 严格递减 的最长非空子数组的长度。 示例 1&#xff1a; 输入&#xff1a;nums [1,4,3,3,2] 输出&#xff1a;2 解释&#xff1a; nums 中严格递增的子数组有[1]、[2]、[3]、[3]、[4] 以及 [1,4] 。 nums 中…

【源码+文档+调试讲解】学院网站

摘 要 使用旧方法对冀中工程技师学院网站的信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在冀中工程技师学院网站的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。这次…

Etcd:分布式键值存储的基石

Etcd 是一个分布式的、一致性的键值存储系统&#xff0c;由 CoreOS 设计并开源。它主要用于共享配置和服务发现&#xff0c;并且被广泛应用于 Kubernetes、Docker 和其他云原生工具中作为核心组件之一。Etcd 使用 Raft 一致性算法来保证数据的一致性&#xff0c;使得它非常适合…

MinerU 是一款将PDF转化如markdown、json工具

MinerU 项目简介 MinerU是一款将PDF转化为机器可读格式的工具&#xff08;如markdown、json&#xff09;&#xff0c;可以很方便地抽取为任意格式。 MinerU诞生于书生-浦语的预训练过程中&#xff0c;我们将会集中精力解决科技文献中的符号转化问题&#xff0c;希望在大模型时…

Day23 第十站 文件IO的多路复用

#include <myhead.h>void insert_client(int *client_arr,int *len,int client) {//client_arr[n]{3,4} len&client_count,client_count2;//添加 5 client_arr[2(*len)]5(client)client_arr[*len]client;(*len); } int find_client(int *client_arr,int len,int clie…