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://xiahunao.cn/news/3267425.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Java的类加载机制

Java的类加载机制是指将类的字节码文件&#xff08;.class文件&#xff09;加载到JVM中并将其转换为Class对象的过程。这个过程由类加载器&#xff08;ClassLoader&#xff09;完成。Java的类加载机制具有动态性和灵活性&#xff0c;使得Java能够支持动态加载类、实现模块化开发…

C++ 八股(2)

1.函数调用的参数是以什么顺序压栈的&#xff0c;为什么&#xff1f; 从右向左压栈的。因为C, C支持可变参函数。 可变参函数就是参数个数可变的函数&#xff0c;如printf()就是可变参函数 void func(int a,...){} 2.有一个函数 在main函数中通过&#xff1a;string s fun…

数据库-触发器,存储过程

按照题目要求完成下列题目&#xff1a; 1.触发器 mysql> use mydb16_trigger; Database changed mysql> create table goods(-> gid char(8) primary key,-> name varchar(10),-> price decimal(8,2),-> num int); Query OK, 0 rows affected (0.01 sec)my…

工作流 Flowable

工作流包括业务流和审批流等业务流程。 在一个流程系统中&#xff0c;任务间往往存在复杂的依赖关系&#xff0c;为保证pipeline的正确执行&#xff0c;就是要解决各任务间依赖的问题&#xff0c;这样DAG结合拓扑排序是解决存在依赖关系的一类问题的利器。DAG ( Directed Acyc…

免费【2024】springboot 毕业生学历证明系统

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

Android AI应用开发:移动检测

基于Google ML模型的Android移动物体检测应用——检测、跟踪视频中的物体 A. 项目描述 ML Kit物体检测器可以对视频流进行操作&#xff0c;能够检测视频中的物体并在连续视频帧中跟踪该物体。 相机捕捉视频时&#xff0c;检测到移动物体并为其生成一个边界框&#xff0c;并分…

微信小程序-本地部署(前端)

遇到问题&#xff1a;因为是游客模式所以不能修改appID. 参考链接&#xff1a;微信开发者工具如何从游客模式切换为开发者模式&#xff1f;_微信开发者工具如何修改游客模式-CSDN博客 其余参考&#xff1a;Ego微商项目部署&#xff08;小程序项目&#xff09;&#xff08;全网…

Xstate inspect状态图的使用 和 原理

状态图的好处之一是&#xff0c;在将状态图组合在一起的过程中&#xff0c;您可以探索流程中所有可能的状态。这种探索将帮助您避免代码中的错误和错误&#xff0c;因为您更有可能涵盖所有可能发生的情况。 因为状态图是可执行的&#xff0c;所以它们既可以表现为图&#xff0…

K8s集群prometheus镜像配置非root用户执行导致监控页面targets无相关node信息监控

【问题描述】 K8s集群prometheus镜像配置非root用户执行导致监控页面targets无相关node信息监控 【问题记录】 在k8s容器集群来部署prometheus服务,实现对node节点的监控,但发现部署了prometheus后页面targets没有显示node信息,配置检查也是正确的 排查prometheus日志发现…

速递!OpenAI 凌晨发布 SearchGPT 进军 AI 搜索,正式与 Google、Perplexity 竞争

&#x1f431; 个人主页&#xff1a;TechCodeAI启航&#xff0c;公众号&#xff1a;TechCodeAI &#x1f64b;‍♂️ 作者简介&#xff1a;2020参加工作&#xff0c;专注于前端各领域技术&#xff0c;共同学习共同进步&#xff0c;一起加油呀&#xff01; &#x1f4ab; 优质专…

数据分析或处理中关于坐标系的一些事

通过对本文的阅读&#xff0c;你将获取坐标系的一些基础知识&#xff0c;以及学会如何使用pyproj实现地理数据的投影与转换。更重要的是&#xff0c;作为一个开发者&#xff0c;面对地理坐标系的图层数据&#xff0c;需要进行面积计算、距离量测、规则分块等需求时&#xff0c;…

成都哪一个新媒体产业园可以提供全方位的支持?全成都拥有最优质服务的园区在这!

成都市&#xff0c;作为中国西部的经济和文化中心&#xff0c;近年来在新媒体产业领域迅猛发展。众多新媒体产业园在这里拔地而起&#xff0c;为创新企业提供了丰富的资源和优质的服务。其中&#xff0c;位于成都金牛区的国际数字影像产业园尤为引人注目&#xff0c;作为园区运…

深入分析 Android ContentProvider (五)

文章目录 深入分析 Android ContentProvider (五)ContentProvider 的性能优化和实践案例1. 性能优化技巧1.1. 数据库索引优化示例&#xff1a;添加索引 1.2. 批量操作与事务管理示例&#xff1a;批量插入操作 1.3. 使用异步操作示例&#xff1a;使用 AsyncTask 进行异步查询 1.…

无人机图像目标检测技术详解

当前研究领域的热点之一。无人机搭载的高清摄像头能够实时捕获大量图像数据&#xff0c;对这些数据进行有效的目标检测对于军事侦察、环境监测、灾害救援等领域具有重要意义。本文将对无人机图像目标检测技术进行详解&#xff0c;包括图像处理技术、目标检测算法、关键技术应用…

通过IEC104转MQTT网关轻松接入阿里云平台

随着智能电网和物联网技术的飞速发展&#xff0c;电力系统中的传统IEC 104协议设备正面临向现代化、智能化转型的迫切需求。阿里云作为全球领先的云计算服务提供商&#xff0c;其强大的物联网平台为IEC 104设备的接入与数据处理提供了强大的支持。本文将深入探讨钡铼网关在MQTT…

springcloud接入seata管理分布式事务

下载安装包 链接: seata 配置seata-server 文件上传Linux解压 压缩包我放在/usr/local/seata中 tar -zxvf seata-server-2.0.0.tar.gz修改配置文件 设置nacos为注册和配置中心 进入文件夹 cd /usr/local/seata/seata/conf修改application.yml文件 ...... ...... cons…

Anaconda目录

安装目录 Anaconda 在默认情况下会安装到 C:\ProgramData\Anaconda3&#xff0c;而 conda 环境和包会安装在 C:\Users\username\.conda\ 目录下。 备注&#xff1a;我是在windows下安装 的Anaconda。我的安装目录是C:\Program Files\Anaconda3 pkgs目录 在以上两个目录下都有…

【React】组件:全面解析现代前端开发的基石

文章目录 一、什么是组件&#xff1f;二、组件的类型三、组件的生命周期四、状态管理五、属性传递六、组合与继承七、最佳实践 在现代前端开发中&#xff0c;React 已成为开发者构建用户界面的首选框架之一。React 的强大之处在于其组件化设计&#xff0c;允许开发者将 UI 拆分…

如何使用C#快速创建定时任务

原文链接&#xff1a;https://www.cnblogs.com/zhaotianff/p/17511040.html 使用Windows的计划任务功能可以创建定时任务。 使用schtasks.exe可以对计划任务进行管理&#xff0c;而不需要编写额外代码 这里掌握schtasks /CREATE 的几个核心参数就可以快速创建计划任务 /SC …

PGSQL学习-基础表结构

1 访问数据库 创建好数据库后&#xff0c;你可以有三种方式访问数据库 运行PostgreSQL的交互式终端程序&#xff0c;它被称为psql&#xff0c; 它允许你交互地输入、编辑和执行SQL命令。 使用一种已有的图形化前端工具&#xff0c;比如pgAdmin或者带ODBC或JDBC支持的办公套件…