【十九】【C++】 priority_queue简单使用和仿函数

priority_queue文档介绍翻译

优先队列是一种容器适配器,专门设计成其中的第一个元素始终是根据某种严格的弱排序准则最大的元素。

这种上下文类似于堆,其中元素可以在任何时刻插入,而只能检索最大堆元素(在优先队列中顶部的元素)。

优先队列被实现为容器适配器,这些适配器是使用特定容器类的封装对象作为其底层容器的类,提供一组特定的成员函数来访问其元素。元素从特定容器的“后面”弹出,这被称为优先队列的顶部。

底层容器可以是任何标准容器类模板或其他特定设计的容器类。容器必须通过随机访问迭代器进行访问,并支持以下操作:

  • empty()

  • size()

  • front()

  • push_back()

  • pop_back()

标准容器类vectordeque满足这些要求。默认情况下,如果没有为特定优先队列类实例化指定容器类,则使用标准容器vector

需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器会在需要时自动调用算法函数make_heappush_heappop_heap来完成这一点。

简单使用

优先队列(Priority Queue)是一种特殊的队列,它的出队顺序是按照元素的优先级决定的,而不是元素进入队列的先后顺序。在C++中,优先队列通常通过STL中的priority_queue容器来实现。priority_queue<queue>头文件中定义,它可以让你轻松地实现一个能够根据元素优先级进行动态排序的队列。

基本操作

插入元素(push):将一个元素添加到优先队列中。

获取顶部元素(top):返回优先级最高的元素,但不从队列中移除它。

删除顶部元素(pop):移除优先级最高的元素。

检查队列是否为空(empty):检查优先队列是否没有任何元素。

获取队列的大小(size):返回优先队列中的元素数量。

示例代码

下面是一个使用priority_queue的基本示例:

 
#include <iostream>
#include <queue>int main() {// 创建一个int类型的优先队列,默认是最大堆std::priority_queue<int> pq;// 插入元素pq.push(30);pq.push(10);pq.push(20);pq.push(5);// 循环直到优先队列为空while (!pq.empty()) {// 打印顶部元素std::cout << pq.top() << " ";// 移除顶部元素pq.pop();}return 0;
}

最小堆优先队列

默认情况下,C++的priority_queue使用std::less<T>作为比较函数,这意味着它将实现为一个最大堆,优先级最高的(值最大的)元素会被放在队列的前面。

如果你想创建一个最小堆,使得优先级最低的元素先出队,你可以通过传递一个比较函数来实现。例如:

 
#include <iostream>
#include <queue>
#include <vector>
#include <functional>int main() {// 创建一个最小堆优先队列std::priority_queue<int, std::vector<int>, std::greater<int>> pq;pq.push(30);pq.push(10);pq.push(20);pq.push(5);while (!pq.empty()) {std::cout << pq.top() << " ";pq.pop();}return 0;
}

优先队列默认创建最大堆

 
/*优先队列默认创建大堆*/
#if 1
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;void TestPriorityQueue1(){// 默认按照less方式比较,创建的是大堆priority_queue<int> q;q.push(5);q.push(4);q.push(8);q.push(1);q.push(9);q.push(2);q.push(6);q.push(3);q.push(7);q.push(0);cout << q.size() << endl;cout << q.top() << endl;q.pop();q.pop();q.pop();cout << q.size() << endl;cout << q.top() << endl;}int main(){TestPriorityQueue1();}#endif

优先队列主动创建最小堆

 
/*优先队列默认创建最小堆*/
#if 1
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
// greater是STL提供的以大于方式比较两个元素的类模板// 在使用时一定要<functional>的头文件
void TestPriorityQueue2(){// 如果要创建小堆,则元素必须按照大于的方式比较priority_queue<int,vector<int>, greater<int>> q;q.push(5);q.push(4);q.push(8);q.push(1);q.push(9);q.push(2);q.push(6);q.push(3);q.push(7);q.push(0);cout << q.size() << endl;cout << q.top() << endl;q.pop();q.pop();q.pop();cout << q.size() << endl;cout << q.top() << endl;}int main(){TestPriorityQueue2();}#endif

优先队列日期类创建最大堆

 
/*优先队列日期类最大堆*/
#if 1
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return _day < d._day;}bool operator>(const Date& d)const{return _day > d._day;}void Show()const{cout<<_year<<"-"<<_month<<"-"<<_day<<endl;}private:int _year;int _month;int _day;};void TestPriorityQueue3(){priority_queue<Date> q;Date d1(2023, 4, 22);Date d2(2023, 4, 21);Date d3(2023, 4, 23);q.push(d1);q.push(d2);q.push(d3);while (!q.empty()) {q.top().Show();q.pop();}}int main(){TestPriorityQueue3();}#endif

优先队列范围迭代器创建对象

 
/*优先队列范围迭代器创建对象*/
#if 1#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;void TestPriorityQueue5(){vector<int> v{ 3,2,7,6,0,4,1,9,8,5 };priority_queue<int> q(v.begin(), v.end());}
int main(){TestPriorityQueue5();}#endif

优先队列日期类创建最小堆

 
/*优先队列日期类创建最小堆*/
#if 1
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return _day < d._day;}bool operator>(const Date& d)const{return _day > d._day;}void Show()const{cout<<_year<<"-"<<_month<<"-"<<_day<<endl;}private:int _year;int _month;int _day;};void TestPriorityQueue4(){priority_queue<Date, vector<Date>, greater<Date>> q;Date d1(2023, 4, 22);Date d2(2023, 4, 21);Date d3(2023, 4, 23);q.push(d1);q.push(d2);q.push(d3);while (!q.empty()) {q.top().Show();q.pop();}}
int main(){TestPriorityQueue4();}#endif

自定义判断函数,函数指针

 
/*自定义判断函数,函数指针*/
#if 1
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return _day < d._day;}bool operator>(const Date& d)const{return _day > d._day;}void Show()const{cout<<_year<<"-"<<_month<<"-"<<_day<<endl;}friend bool CompareDate(const Date* left, const Date* right);friend class Compare;
private:int _year;int _month;int _day;};bool CompareDate(const Date* left, const Date* right){return left->_day < right->_day;}typedef bool (*COM)(const Date* left, const Date* right);void TestPriorityQueue6(){priority_queue<Date*, vector<Date*>, COM> q(CompareDate);Date d1(2023, 4, 22);Date d2(2023, 4, 21);Date d3(2023, 4, 23);q.push(&d1);q.push(&d2);q.push(&d3);while (!q.empty()) {Date* top = q.top(); // 获取优先队列顶部元素top->Show();         // 显示日期q.pop();             // 移除顶部元素}}int main(){TestPriorityQueue6();}

typedef bool (*COM)(const Date* left, const Date* right);

这行代码定义了一个函数指针类型COM,用于指向一个特定的函数签名。这个签名要求函数接受两个指向Date对象的常量指针作为参数,并返回一个bool类型的结果。在这个上下文中,这样的函数通常用于比较两个Date对象,并根据比较结果(真或假)来排序或做出决策。

具体来说,typedef关键字在C++中用于为现有的类型定义一个新的名称。在这个例子中,它定义了COM作为一种新的类型,这种类型是指向函数的指针,这些函数具有特定的参数列表和返回类型:

  • 参数列表:两个const Date*类型的参数,表示指向常量Date对象的指针。使用指针允许函数不改变原始对象的状态,同时使用const确保函数内部不会修改这些对象。

  • 返回类型:bool,用于表示比较的结果。在排序或其他需要比较的场景中,返回值通常用于指示第一个参数是否在排序顺序上位于第二个参数之前。

priority_queue<Date*, vector<Date*>, COM> q(CompareDate);

这行代码创建了一个 std::priority_queue,其中包含了指向 Date 对象的指针。在这个 priority_queue 的声明中,使用了三个模板参数:

  • 元素类型:Date*,表示队列中存储的元素是指向 Date 对象的指针。

  • 底层容器:std::vector<Date*>,表示优先队列使用 std::vector 来存储这些指针。std::vector 是一个动态数组,适用于几乎所有类型的容器操作,而且在这里特别适合作为优先队列的底层数据结构。

  • 比较类型:COM,这是一个指向函数的指针类型,用于比较两个 Date 对象。在这个上下文中,COM 是通过之前定义的 typedef 创建的,指向一个接受两个 const Date* 类型参数的函数,并返回一个 bool 类型的结果。

接下来的部分 q(CompareDate);priority_queue构造函数调用,它接受一个名为 CompareDate 的函数作为参数。这个函数符合 COM 类型的要求,用于确定队列中元素的优先顺序。在这个例子中,CompareDate 函数基于日期对象的 _day 字段来比较日期,以决定它们在队列中的顺序。

这种方式允许自定义优先队列的排序准则,而不是仅依赖于元素类型的默认比较操作(例如,如果是基本类型或具有定义了 < 运算符的类型)。通过提供自定义比较函数,用户可以控制哪些元素应该被视为优先级更高,这在处理复杂数据类型或有特定排序需求的场合特别有用。

仿函数

在C++中,仿函数(Function Object或Functor)是一个行为类似函数的对象。具体来说,仿函数是通过重载 operator() 运算符的类实例,这让类的实例可以像函数一样被调用。仿函数提供了比普通函数指针更多的灵活性和功能,因为它们可以拥有自己的状态。

仿函数的优点

状态保持:与普通函数不同,仿函数可以有自己的状态。你可以在多次调用之间保持和修改这些状态。

内联调用:仿函数的调用可以被编译器内联,这意味着可以减少函数调用的开销,提高效率。

泛型编程:仿函数可以搭配标准模板库(STL)中的算法和容器使用,非常适合实现自定义的比较逻辑或操作。

示例:自定义比较仿函数

假设你想在 priority_queue 中使用自定义的比较逻辑,可以定义一个仿函数来实现这一点。以下是一个简化的 Date 类仿函数比较的例子:

 
#include <iostream>
#include <queue>
using namespace std;class Date {
public:int year, month, day;Date(int y, int m, int d) : year(y), month(m), day(d) {}
};// 自定义比较仿函数
class CompareDate {
public:bool operator()(const Date& lhs, const Date& rhs) const {// 比较逻辑:先年后月再日if (lhs.year != rhs.year) return lhs.year > rhs.year;if (lhs.month != rhs.month) return lhs.month > rhs.month;return lhs.day > rhs.day;}
};int main() {priority_queue<Date, vector<Date>, CompareDate> pq;pq.push(Date(2023, 4, 22));pq.push(Date(2023, 5, 21));pq.push(Date(2023, 3, 23));while (!pq.empty()) {Date d = pq.top();cout << d.year << "-" << d.month << "-" << d.day << endl;pq.pop();}return 0;
}

在这个例子中,CompareDate 是一个仿函数,它重载了 operator() 来提供 Date 对象的比较逻辑。这个逻辑首先比较年份,如果年份相同,则比较月份,如果月份也相同,最后比较日。这个仿函数可以用作 priority_queue 的比较类型参数,从而定制容器中元素的排列顺序。

优先队列与仿函数结合

 
/*优先队列与仿函数结合*/
#if 1
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return _day < d._day;}bool operator>(const Date& d)const{return _day > d._day;}void Show()const{cout<<_year<<"-"<<_month<<"-"<<_day<<endl;}friend bool CompareDate(const Date* left, const Date* right);friend class Compare;
private:int _year;int _month;int _day;};bool CompareDate(const Date* left, const Date* right){return left->_day < right->_day;}typedef bool (*COM)(const Date* left, const Date* right);
// 仿函数--函数对象:可以像函数一样使用的对象
class Compare
{
public:bool operator()(const Date* left, const Date* right){return left->_day < right->_day;}};void TestPriorityQueue7(){Date d1(2023, 4, 22);Date d2(2023, 4, 21);Date d3(2023, 4, 23);CompareDate(&d1, &d2);Compare com;com(&d1, &d2);com.operator()(&d1, &d2);priority_queue<Date*, vector<Date*>, Compare> q;q.push(&d1);q.push(&d2);q.push(&d3);while (!q.empty()) {Date* top = q.top(); // 获取优先队列顶部元素top->Show();         // 显示日期q.pop();             // 移除顶部元素}}int main(){TestPriorityQueue7();return 0;}
#endif

对于优先队列比较参数的探究

 
priority_queue<Date*, vector<Date*>, Compare> q;//仿函数
priority_queue<Date, vector<Date>, CompareDate> pq;//仿函数
priority_queue<Date*, vector<Date*>, COM> q(CompareDate);//函数指针
priority_queue<Date, vector<Date>, greater<Date>> q; //默认greater对象

我们发现第三个参数,比较大小的参数,可以有上面三种使用方法。第一个和第二个是一样的,都是仿函数,仿函数本质是对象,和第四个同样是传入对象,而第三个传入的是函数指针。说明第三个参数可以接收函数指针也可以接收对象。

仿函数和函数指针的区别

虽然从表面上看,仿函数(通过类重载的 operator())和函数指针(指向一个具体函数的指针)都可以作为排序准则传递给 std::priority_queue(或其他模板类),它们在底层实现和使用方式上存在一些关键差异:

仿函数(Function Objects):仿函数是通过类实例化的对象,这些对象通过重载 operator() 来实现。它们可以有自己的状态(成员变量)并可以携带更多的上下文信息。在模板参数中传递仿函数类型时,你实际上传递的是一个类型(而非对象实例本身),std::priority_queue 在内部会创建这个类型的实例来进行元素比较。如果需要传递状态或参数给仿函数,你需要在仿函数类中提供相应的构造函数。

函数指针:函数指针是指向函数的指针,它不能携带状态(除非使用全局变量或静态变量,但这通常不是一个好的设计选择)。在模板参数中使用函数指针时,如 COM 类型,你实际上是在传递一个指向函数的指针,这意味着传递的是一个地址值。当你在构造 std::priority_queue 时提供 CompareDate 函数作为参数,你实际上是在提供一个具体的函数指针值。

对象与地址的传递

对于仿函数,你传递的是一个类型,std::priority_queue 使用这个类型在内部构造一个对象实例。这个过程并不涉及显式的地址传递,而是类型信息的传递。

对于函数指针,传递的确实是一个地址值,即函数在内存中的位置。

默认比较对象

当使用如 std::greater<Date> 这样的默认比较对象时,同样是传递了一个类型给 std::priority_queue,它会构造这个类型的实例来进行比较。std::greater<T>std::less<T> 是标准库提供的比较函数对象类型,它们也是通过重载 operator() 实现的。

优先队列创建对象的方式

使用默认比较函数对象

默认情况下,std::priority_queue使用std::less<T>作为其比较函数对象,构造一个最大堆。这意味着没有指定比较函数时,队列中的最大元素将会位于顶部。

 
std::priority_queue<int> pq; // 默认为最大堆,使用 std::less<int> 比较元素

使用标准库比较函数对象

可以通过显式指定比较函数对象(如std::greater<T>)来改变优先队列的排序行为,例如构建一个最小堆。

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

使用自定义仿函数

通过定义一个自定义的比较类(仿函数),可以实现更复杂的比较逻辑。该类需要重载operator()

 
class Compare {
public:bool operator()(int a, int b) {// 自定义比较逻辑return a > b; // 示例:构建最小堆}
};
std::priority_queue<int, std::vector<int>, Compare> customHeap;

使用函数指针

如果你有一个静态比较函数,也可以将其地址作为优先队列的比较逻辑。这通常适用于简单的比较逻辑或C风格的代码。

 
bool compare(int a, int b) { return a > b; } // 函数指针比较
// 注意:直接使用函数指针作为模板参数在标准的 priority_queue 中不直接支持,需要包装或转换为对象。

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

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

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

相关文章

一周学会Django5 Python Web开发-项目配置settings.py文件-资源文件配置

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计17条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

(N-144)基于微信小程序在线订餐系统

开发工具&#xff1a;IDEA、微信小程序 服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 前端技术&#xff1a;vue、ElementUI、 Vant Weapp 服务端技术&#xff1a;springbootmybatisredis 本系统分微信小程序和…

搜索Agent方案

为啥需要整体方案&#xff0c;直接调用搜索接口取Top1返回不成嘛&#xff1f;要是果真如此Simple&Naive&#xff0c;New Bing岂不是很容易复刻->.-> 我们先来看个例子&#xff0c;前一阵火爆全网的常温超导技术&#xff0c;如果想回答LK99哪些板块会涨&#xff0c;你…

StarUML无法安装扩展的解决方案

StarUML无法安装扩展解决方案 版本&#xff1a;StarUML3.2.2 遇到问题 Unable to access the extension registry, Please try again later. 解决方案 第一步 https://docs.staruml.io/user-guide/managing-extensions#install-extension官网给了怎么手动安装扩展器的方法…

力扣1732. 找到最高海拔(前缀和)

Problem: 1732. 找到最高海拔 文章目录 题目描述思路及解法复杂度Code 题目描述 思路及解法 1.求取数组gain的大小 n n n; 2.定义一个大小为 n 1 n 1 n1的数组preSum; 3.先求取前 n n n个元素的前缀和&#xff0c;再最后单独处理preSum[n];其中preSum[n] preSum[n - 1] gai…

<代码整洁之道>精彩片段整理

最近在读这本<代码整洁之道>&#xff0c;感觉里面有很多内容都很有启发。整理下来&#xff0c;大家一起看下&#xff0c;顺便看看作者说的有没有道理。这本书的作者是&#xff1a;罗伯特丶马丁&#xff0c;大家经常叫他鲍勃大叔。下面直接进入正题&#xff0c;先看一下目…

【MySQL】操作库 —— 表的操作 -- 详解

一、增加表 1、创建表 mysql> create database [if not exists] table_name ( -> field1 datatype, -> field2 datatype, -> field3 datatype -> ) character set 字符集 collate 校验规则 engine 存储引擎; 注意 &#xff1a;最后一行也可以写成&#x…

一、ActiveMQ介绍

ActiveMQ介绍 一、JMS1.jms介绍2.jms消息传递模式3.jms编码总体架构 二、消息中间件三、ActiveMQ介绍1.引入的原因1.1 原因1.2 遇到的问题1.3 解决思路 2.定义3.特点3.1 异步处理3.2 应用系统之间解耦3.3 实际-整体架构 4.作用 一、JMS 1.jms介绍 jms是java消息服务接口规范&…

基于PSO优化的LSTM多输入回归预测(Matlab)粒子群优化长短期神经网络回归预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、部分代码展示&#xff1a; 四、完整代码数据分享下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matl…

哈希切分

目录 一 二 三 2.单个子文件太大怎么办&#xff1f;&#xff08;分两种情况讨论&#xff09; 一 这样的题目典型就是KV模型的问题&#xff0c;即通过key IP找对应的value 出现次数&#xff0c;对于KV模型的问题首先想到的就是用map来统计次数&#xff0c;但是100G大小的文件…

【计算机网络】多路复用和多路分解

多路分解 demultiplexing 数据到达接收主机时&#xff0c;需要指定对应的套接字&#xff0c;所以在运输层报文段中放置了一些字段用于套接字的识别&#xff0c;从而将报文段定向到套接字&#xff0c;将运输层报文段数据交付到正确套接字的工作就是多路分解。多路复用 multiple…