C++第二十七弹---优先级队列的高级应用:结合仿函数优化性能

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

目录

1 priority_queue的介绍和使用

1.1 priority_queue的介绍

1.2 priority_queue的使用

2 仿函数的介绍和使用

2.1 仿函数的介绍 

2.2 仿函数的使用

3 自定义类型的使用

4 指针类型的使用

5 priority_queue的模拟实现

总结


1 priority_queue的介绍和使用


1.1 priority_queue的介绍

1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。

2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。

3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,priority_queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。

4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:

  • empty():检测容器是否为空
  • size():返回容器中有效元素个数
  • front():返回容器中第一个元素的引用
  • push_back():在容器尾部插入元素
  • pop_back():删除容器尾部元素


5. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。

6. 需要支持随机访问迭代器以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。

1.2 priority_queue的使用


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


默认情况下priority_queue是大堆。
 

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

【注意】
1. 默认情况下,priority_queue是大堆。

代码演示:

#include <iostream>
#include <queue>
using namespace std;int main()
{//默认less 大堆priority_queue<int> pq;//实例化堆pq.push(1);//插入值pq.push(4);pq.push(2);pq.push(3);//堆不为空时打印堆顶元素,大堆打印出来为降序while (!pq.empty()){cout << pq.top() << " ";//打印堆顶元素pq.pop();//删除堆顶元素}cout << endl;return 0;
}

代码测试: 

 通过上面的代码,我们能够简单的使用优先级队列了,但是在STL库中,优先级队列有三个类模板参数,使用如下:

声明:

template <class T, class Container = vector<T>,  
class Compare = less<typename Container::value_type> > 
class priority_queue;

使用: 

priority_queue<int, vector<int>, greater<int>> pq1;
pq1.push(1);
pq1.push(4);
pq1.push(3);
pq1.push(2);
while (!pq1.empty())
{cout << pq1.top() << " ";pq1.pop();
}
cout << endl;

模板参数解释:

class Container = vector<T>:

这是用来内部存储优先级队列中元素的容器类型。默认是 std::vector,但也可以是其他符合要求的容器类型,比如 std::deque。但是需要注意的是,必须支持随机访问迭代器(Random Access Iterator),以及 front(),push_back(),pop_back() 的操作。

class Compare = less<typename Container::value_type>:

这是用来比较元素优先级的比较函数对象。默认是 std::less,该函数使得最大的元素被认为是最高优先级(形成最大堆)。如果想要最小的元素为最高优先级(形成最小堆),可以通过提供 std::greater 函数对象作为这个模板参数来改变这个行为。

2 仿函数的介绍和使用

优先级队列默认使用less这个仿函数,如果我们需要建立小堆,需要自己传参:

priority_queue<int,vector<int>,greater<int>> pq;

2.1 仿函数的介绍 

什么是仿函数?

在C++中,仿函数是一种使用对象来模拟函数的技术。它们通常是通过类实现的,该类重载了函数调用操作符(operator())。

2.2 仿函数的使用

// 定义一个仿函数模板类
template<class T>
struct Less
{// 重载函数调用操作符bool operator()(const T& x, const T& y){return x < y;}
};int main()
{Less<int> lessfunc;//创建有名对象cout << lessfunc(1, 2) << endl;//有名对象调用类成员函数cout << lessfunc.operator()(1, 2) << endl;//显示调用cout << Less<int>()(1, 2) << endl;//匿名对象,前面一个括号构造对象,后面调用函数cout << Less<int>().operator()(1, 2) << endl;return 0;
}

在上面例子中,我们定义了一个名为 Less 的仿函数类,它重载了 operator() 来实现前面一个数是否小于后面一个数的功能。

在 main 函数中创建了该类的一个实例 lessfunc 并且像调用函数一样使用 lessfunc来比较两数是否属于小于关系。

仿函数广泛用于C++标准库中,特别是在算法(std::sort)中作为比较函数或者操作函数,以及在容器(如 std::set 或者 std::map)中作为排序准则。

举例如下:

#include <algorithm>
#include <iostream>
#include <vector>
#include <functional>
using namespace std;int main()
{vector<int> v = { 1,5,9,7,3,4 };//算法库中的排序,默认升序sort(v.begin(), v.end());for (auto e : v){cout << e << " ";}cout << endl;//降序,greater >  ,函数传的对象,()为匿名对象//函数参数传递的是对象,为了方便,此处使用匿名对象sort(v.begin(), v.end(), greater<int>());for (auto e : v){cout << e << " ";}cout << endl;
}

在上面的例子中,greater 仿函数用来定义一个降序规则,随后在 std::sort 中将其实例化并传递给算法进行降序排序。

仿函数的一个主要优点是它们可以保持状态,这意味着它们可以在多次调用之间保存和修改信息。这使得它们非常灵活和强大。此外,由于它们是类的实例,它们也可以拥有额外的方法和属性。

3 自定义类型的使用

如果在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 priority_queue_test3()
{// 如果要创建小堆,需要用户提供>的重载lin::priority_queue<Date, vector<Date>, lin::Greater<Date>> pq;Date d1(2024, 1, 1);//有名对象pq.push(d1);pq.push(Date(2024,1,3));//匿名对象pq.push({2024,1,2});//多参数隐式类型转换while (!pq.empty()){cout << pq.top() << " ";pq.pop();}cout << endl;
}

 测试结果:

4 指针类型的使用

 代码举例:

void priority_queue_test3()
{//指针类型,内置类型,不能运算符重载,指针的大小比较是随机的lin::priority_queue<Date*, vector<Date*>, lin::Greater<Date*>> pqptr;pqptr.push(new Date(2024, 1, 1));pqptr.push(new Date(2024, 1, 3));pqptr.push(new Date(2024, 1, 2));while (!pqptr.empty()){cout << *(pqptr.top()) << " ";pqptr.pop();}cout << endl;
}

 测试结果:

由于优先级队列中存放的地址,每次动态开辟的地址是随机的,因此运行的结果也是随机的。

我们实际的目的是想通过日期比较大小,即对指针解引用比较大小,解决方案如下:

  • 1、对指针进行运算符重载,但是指针是内置类型,不能运算符重载。
  • 2、使用仿函数,即我们可以使用一个专门对日期类指针比较大小的仿函数或者使用模板类比较大小的仿函数。

日期类仿函数

class GreaterPDate
{
public:bool operator()(const Date* p1, const Date* p2){return *p1 < *p2;}
};

模板类仿函数

template<class T>
class GreaterPT
{
public:bool operator()(const T* p1, const T* p2){return *p1 < *p2;}
};

优化后代码

void priority_queue_test3()
{lin::priority_queue<Date*, vector<Date*>, GreaterPDate> pqptr;//lin::priority_queue<Date*, vector<Date*>, GreaterPT> pqptr;pqptr.push(new Date(2024, 1, 1));pqptr.push(new Date(2024, 1, 3));pqptr.push(new Date(2024, 1, 2));while (!pqptr.empty()){cout << *(pqptr.top()) << " ";pqptr.pop();}cout << endl;
}

5 priority_queue的模拟实现


priority_queue的底层结构就是堆,因此此处只需对堆进行通用的封装即可。

博主在数据结构专栏详细讲解了堆的实现,此处则不做详细讲解,感兴趣的uu可以看博主数据结构第十一弹---堆,链接如下:

数据结构第十一弹---堆icon-default.png?t=N7T8https://blog.csdn.net/2201_75584283/article/details/135325855

namespace lin
{//仿函数  对象像函数一样使用//大堆使用小于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;}};//仿函数默认大堆,使用Lesstemplate<class T,class Container = vector<T>,class Com = Less<T>>class priority_queue{public://默认大堆void adjust_up(size_t child){Com com;//创建仿函数对象size_t 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 = (parent - 1) / 2;}else{break;}}}void push(const T& x){_con.push_back(x);//尾插数据adjust_up(_con.size() - 1);//向下调整}void adjust_down(size_t parent){size_t child = parent * 2 + 1;Com com;while (child < _con.size()){//假设法,假设左孩子更大,如果右孩子大则++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);//向下调整}const T& top(){return _con[0];//堆顶元素为第一个元素}bool empty(){return _con.empty();}size_t size(){return _con.size();}private:Container _con;};
}

总结

本篇博客就结束啦,谢谢大家的观看,如果公主少年们有好的建议可以留言喔,谢谢大家啦!

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

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

相关文章

Python升级打怪—Django入门

目录 一、Django简介 二、安装Django 三、创建Dajngo项目 (一) 创建项目 (二) 项目结构介绍 (三) 运行项目 (四) 结果 一、Django简介 Django是一个高级Python web框架&#xff0c;鼓励快速开发和干净、实用的设计。由经验丰富的开发人员构建&#xff0c;它解决了web开…

【文件fd】文件描述符fd | 文件描述表

目录 1.文件描述符fd 2.系统调用的0/1/2 3.C语言的stdin/stdout/stderr 4.系统调用的0/1/2和C语言的stdin/stout/stderr二者的关系❓ 5.文件描述表 5.1 文件描述符概念 5.3 文件对象strcut file 5.4 进程和文件对应关系 5.5 文件描述符理解 5.6 源码查看 1.文件描述…

谷粒商城实战笔记-55-商品服务-API-三级分类-修改-拖拽数据收集

文章目录 一&#xff0c;拖拽后结点的parentCid的更新二&#xff0c;拖拽后结点的父节点下所有结点的sort排序属性的变化更新排序的逻辑代码分析 三&#xff0c;拖拽后结点及其子节点catLevel的变化判断是否需要更新 catLevel获取拖动后的新节点 更新 catLevel完整代码 这一节的…

mysql特殊字符、生僻字存储设置

mysql utf-8模式下&#xff0c;分为ut8mb3,utf8mb4&#xff0c;mb4是支持特殊字符、emoji表情的&#xff0c;mb3是不支持的。 报错信息&#xff1a; 1### Error updating database. Cause: java.sql.SQLException: Incorrect string value: \xF0\xA8\x92\x82\xE6\x95... fo…

MongoDB教程(二十):MongoDB正则表达式

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、正则表…

【ESP32 idf 硬件I2C驱动MPU6050获取六轴数值】

目录 I2C介绍配置安装驱动通信创建&删除命令链接容器起始时序写数据读数据结束时序开始命令 mpu6050 硬件i2c驱动代码&调试代码调试 I2C 介绍 介绍部分可以看我写的【ESP32 idf 软件模拟I2C驱动MPU6050实现六轴加速度的获取】&#xff0c;这个是使用软件模拟的I2C时序…

python在类中手动定义标准化输出函数

在深度学习等训练框架中&#xff0c;有时候需要对模型的名称、参数量、训练进度、中间结果等进行标准化输出&#xff0c;从而方便实时查看代码运行情况&#xff0c;这时&#xff0c;可以在类中手动定义如下标准化的输出函数&#xff0c;然后在需要输出的地方进行调用即可。 首…

鸿蒙北向开发 DevEco Studio 4.1 下载安装傻瓜式教程

开篇 由于鸿蒙处于快速发展中,鸿蒙的api快速迭代更新,老版本的DevEco studio无法支持更新版本的api,因此华为官网放弃了老版本的维护.直接从华为开发者官网无法下载老版本,当前华为开发者官网已经推出next版本了 DevEco studio3.1安装教程 上述教程提供的华为开发者官网地址已经…

linux怎么创建python

第一步&#xff0c;创建一个test文件夹。 第二步&#xff0c;打开终端进入该文件。 第三步&#xff0c;vim test.py。 第四步&#xff0c;编写代码。 第五步&#xff0c;编辑好之后&#xff0c;按Esc键切换到命令模式&#xff0c;然后输入:wq&#xff0c;再按回车键即可自动保存…

探索 SPL-404 协议标准:NFT 与 DeFi 的融合

在快速发展的数字资产领域中&#xff0c;NFT 协议标准持续演变&#xff0c;改变了我们对数字所有权和互动方式的理解。从 Art 到 Gamefi 等等&#xff0c;NFT 已经演变成数字经济的重要组成部分&#xff0c;吸引了广泛关注。遵循 ERC404 协议&#xff0c;SPL404 概念在 Solana …

AvaloniaUI的学习

相关网站 github:https://github.com/AvaloniaUI/Avalonia 官方中文文档&#xff1a;https://docs.avaloniaui.net/zh-Hans/docs/welcome IDE选择 VS2022VSCodeRider 以上三种我都尝试过&#xff0c;体验Rider最好。VS2022的提示功能不好&#xff0c;VSCode太慢&#xff0c…

flex/lex使用和学习

flex/lex用于生成解析配置文件的C代码&#xff0c;我们可以不用自己手动去做解析的工作&#xff0c;交由他们生成的代码去做。 假设&#xff0c;我有如下一个配置文件config.xml 配置文件中定义了三种channel,分别为SSIF, IPMB, NET&#xff0c;每一种channel都有4个int属性&a…

生成式AI:对话系统(Chat)与自主代理(Agent)的和谐共舞

生成式AI&#xff1a;对话与行动的和谐共舞 我们正站在一个令人激动的时代门槛上——生成式AI技术飞速发展&#xff0c;带来了无限的可能性。一个关键问题浮现&#xff1a;AI的未来是对话系统&#xff08;Chat&#xff09;的天下&#xff0c;还是自主代理&#xff08;Agent&am…

实时同步:使用 Canal 和 Kafka 解决 MySQL 与缓存的数据一致性问题

目录 1. 准备工作 2. 将需要缓存的数据存储 Redis 3. 监听 canal 存储在 Kafka Topic 中数据 1. 准备工作 1. 开启并配置MySQL的 BinLog&#xff08;MySQL 8.0 默认开启&#xff09; 修改配置&#xff1a;C:\ProgramData\MySQL\MySQL Server 8.0\my.ini log-bin"HELO…

【Git】merge合并分支

两个分支未修改同一个文件的同一处位置: Git自动合并 两个分支修改了同一个文件的同一处位置:产生冲突 例&#xff1a; 在master分支修改了main同时&#xff0c;feat分支也修改了相同的文件 合并的时候就会产生冲突 解决方法: Step1- 手工修改冲突文件&#xff0c;合并冲突内容…

立仪光谱共焦传感器应用测量之:汽车连接器高度差测量

01 检测要求&#xff0c;要求测量汽车连接器的高度差 02 检测方式 根据观察&#xff0c;我们采用立仪科技光谱共焦H4UC控制器搭配D65A52系列镜头&#xff0c;角度最大&#xff0c;外径最大&#xff0c;量程大&#xff0c;可以有效应用于测量弧面&#xff0c;大角度面等零件。 0…

会员信息管理系统-计算机毕业设计源码38258

目 录 摘要 1 绪论 1.1 研究背景 1.2 研究意义 1.3开发技术 1.3.1 Spring Boot框架 1.3.2 Java语言 1.3.3 MySQL数据库 1.4论文结构与章节安排 2系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 登录流程 2.2.2数据删除流程 2.3 系统功能分析 2.4 系统用例分析…

视频去水印免费电脑版 pdf压缩在线免费网页版 pdf压缩在线免费 简单工具软件详细方法步骤分享

消除视频中的恼人水印&#xff0c;是许多视频编辑爱好者的常见需求。在这篇文章中&#xff0c;我们将探讨几种视频去水印的技巧&#xff0c;在数字化时代&#xff0c;视频和图片的传播越来越方便&#xff0c;但随之而来的水印问题也让人头疼。本文将为您详细介绍视频剪辑去水印…

Web开发:ASP.NET CORE中前端使用Ajax定时获取后端数据

一、低难度&#xff08;刷新a标签&#xff09; 1、需求 给a标签每15s刷新一次&#xff0c;显示最新的时间&#xff08;时间必须由后端获取&#xff09; 应该如何操作呢 2、代码 后端 using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Mi…

医疗器械维修行业发展及趋势

医疗器械维修的前景是广阔的。‌ 随着医疗技术的不断发展和进步&#xff0c;‌医疗器械的种类和数量持续增加&#xff0c;‌对专业维修人员的需求也在不断上升。‌无论是医院、‌诊所等医疗机构&#xff0c;‌还是医疗器械生产企业、‌销售企业等&#xff0c;‌都需要专业的维修…