【C++】优先级队列与仿函数

                                                

🔥个人主页:北辰水墨

🔥专栏:C++学习仓

Alt

本节内容我们来讲解优先级队列和仿函数。文中会附上优先级队列模拟实现的源码。

注意:本节我会把最大优先级队列和大堆名词混着用,他们两个本质是一样的。

一、priority_queue的介绍和使用: 

1.  优先队列是一种容器适配器,它的第一个元素总是它所包含的元素中最大的(大堆)。
2.  此上下文类似于,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。

3.  容器应该可以通过随机访问迭代器访问,并支持以下操作:

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

4.priority_queue优先级队列应该支持以下操作:(点击即可跳转到cplusplus网站查看相关用法)

  • empty()  检测优先级队列是否为空
  • size()      返回优先级队列中有效元素个数
  • top()       取堆顶的元素
  • push()    插入元素,并且向下调整算法,保持堆属性
  • pop()      删除堆顶元素,交换堆顶和最后一个数据,然后向上调整算法,再size--;
  • swap()    交换两个堆中的所有元素

5.  标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector
6.  需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。

函数使用

T:类型模板参数

Container:用来内部存储优先级队列中元素的容器类型。

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

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

🔥构造函数 

 创建一个优先级队列:

创建最大优先级队列(大堆)std::priority_queue<int> q1;

创建最小优先级队列(小堆)std::priority_queue<int,std::vector<int>,std::greater<int>> q2;

🔥empty( )

检测优先级队列是否为空,是返回true,否则返回false

🔥top( )

返回优先级队列中最大(最小元素),即堆顶元素

🔥push( )

在优先级队列中插入元素x

🔥pop( )

删除优先级队列中最大(最小)元素,即堆顶元素

 默认情况下是大堆:

// priority_queue::push/pop
#include <iostream>       // std::cout
#include <queue>          // std::priority_queueint main ()
{std::priority_queue<int> mypq;mypq.push(30);mypq.push(100);mypq.push(25);mypq.push(40);std::cout << "Popping out elements...";while (!mypq.empty()){std::cout << ' ' << mypq.top();mypq.pop();}std::cout << '\n';return 0;
}

它会自动调成大堆,测试结果如下:

        Popping out elements... 100 40 30 25

最后堆中没有元素了,被pop完了。

那我们要建小堆,就需要涉及仿函数。

仿函数的介绍和使用

 创建priority_queue优先级队列的时候,默认使用less这个仿函数。我们也可以手动传入greater仿函数来实现小堆。

这里我要先狡辩一下:为什么传less:建大堆。传greater:建小堆。

注意: 在普通排序的过程中,less是排升序 1 2 3 4 5

                                                greater是排降序 5 4 3 2 1

而在优先级队列中,就很奇怪,我们没办法改变传less:建大堆(降序)。传greater:建小堆(升序),这个事实。我也无法解释,只能记住这个事实。  

我们接下来详细讲解一下什么是仿函数

在C++中,仿函数是一种使用实例化出来的对象来模拟函数的技术。它们通常是通过类实现的,该类重载了函数调用操作符(operator()仿函数可以像普通函数一样被调用,但它们可以拥有状态(即,它们可以包含成员变量,继承自其它类等)

简而言之:一个类,重载()运算符,通过对象调用调用这个()函数。

好处:类模版缺省值可以是一个类,不能是函数。可以把函数指针淘汰了。 

#include<iostream>
class A
{
public:A(){}template<class T>const T& operator()(const T& a,const T& b){return a + b;}
private:
};
int main()
{A aa;std::cout<<aa(2, 4);std::cout<<aa.operator()(2, 4);std::cout<<A()(2, 4);return 0;
}

分析代码:

  1.  看起来就像调用了aa(int,int);这个函数。实际上:

          aa是A类实例化的对象

          aa(2,4);  是去调用()重载运算符的函数

  2. aa.operator()(2, 4);显示调用

  3.  A()(2,4) 使用了匿名对象

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

greater和less 

std::greater 和 std::less 是预定义的函数对象模板,用于执行比较操作。它们定义在<functional>头文件中。std::greater 用来执行大于(>)的比较,而 std::less 用来执行小于(<)的比较 

函数对象模板 std::less 和 std::greater 的实现通常如下:

namespace std {//一个less类,并且是struct出来的
template<class T>
struct less {bool operator()(const T& lhs, const T& rhs) const {return lhs < rhs;}
};//一个greater类
template<class T>
struct greater {bool operator()(const T& lhs, const T& rhs) const {return lhs > rhs;}
};}

 greater和less的常规用法:

// greater example
#include <iostream>     // std::cout
#include <functional>   // std::greater
#include <algorithm>    // std::sort  algorithm算法库,为了调用sort算法int main () {int numbers[]={20,40,50,10,30};std::sort (numbers, numbers+5, std::greater<int>());for (int i=0; i<5; i++)std::cout << numbers[i] << ' ';std::cout << '\n';return 0;
}// less exampleint main () {int foo[]={10,20,5,15,25};int bar[]={15,10,20};std::sort (foo, foo+5, std::less<int>());  // 5 10 15 20 25std::sort (bar, bar+3, std::less<int>());  //   10 15 20if (std::includes (foo, foo+5, bar, bar+3, std::less<int>()))std::cout << "foo includes bar.\n";return 0;
}

细心的同学就会发现:

 std::sort (numbers, numbers+5, std::greater<int>());

 std::priority_queue(int,vector<int>,greater<int>);

怎么一个有括号,一个没有括号呢?

这就要看它的源码是怎么实现的了。

sort函数:class Compare(类型模版)   -->  Compare comp(对象):函数模板,要传对象(可以是匿名对象)

priority_queue类: class Compare (类模版),传类型,会在优先级队列内部构建一个对象

 二、priority_queue的模拟实现:

基本框架:

#include<vector>
#include<iostream>
#include<list>using namespace std;
namespace ink {template<class T, class Container = vector<T>, class Compare = less<T>>class priority_queue{public:void adjust_up(size_t child){}void push(const T& x){}void adjust_down(size_t parent){}void pop(){}bool empty(){}size_t size(){}const T& top(){}private:Container _con; //通过这个Container这个类型,构建一个对象};
}

🔥push( )

优先级队列里面,我们要插入数据,会进行向上调整

所以实现如下

void push(const T& x)
{_con.push_back(x);adjust_up(_con.size() - 1);
}

 🔥pop( )

pop需要删除堆顶的数据,我们的方式是首尾交换,尾删,再向下调整

void pop()
{swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);
}

 🔥empty( )

直接判断即可

bool empty()
{return _con.empty();
}

 🔥size( )

size_t size()
{return _con.size();
}

 🔥top( )

const T& top()
{return _con[0];
}

 接着我们来完成两个关键的函数,向上调整和向下调整

🔥adjust_up( )

 当前位置每次和他的父节点比较

void adjust_up(size_t child)
{int parent = (child - 1) / 2;while (child > 0){if (_con[child]>_con[parent]){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}
  • 对于给定的子节点索引child,其父节点的索引计算为(child - 1) / 2
  • 循环条件:while (child > 0)循环确保我们不会尝试移动根节点(因为根节点的索引为0,没有父节点)。循环继续执行,只要当前节点的索引大于0。
  • 完成交换后,更新child变量为原父节点的索引,因为交换后当前元素已经移动到了父节点的位置。然后,对新的child值重新计算parent索引,继绀执行可能的进一步交换
  • 循环终止条件:如果当前节点的值不小于其父节点的值(即堆的性质得到了满足),循环终止,else break;执行

🔥adjust_down( ) 

void Ajustdown(size_t parent)
{size_t child = parent * 2 + 1;while (child<_con.size()){if (child + 1 < _con.size() && _con[child + 1] >_con[child])//防止只有左孩子而越界{child++;}if (_con[child] >_con[parent]){Swap(&a[child], &a[parent]);parent = child;child = child * 2 + 1;}else{break;}}
}

 两个调整函数的优化

我上面实现的代码只能完成一种堆的实现,如何进行封装使我们能够根据传参实现大堆或小堆呢?

这里就涉及到仿函数了,注意看我们模版中的第三个参数:

template<class T, class Container = vector<T>, class Compare = less<T>>

 我们首先补充greater和less两个类:

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;}};

我们控制大小堆,则需要控制两个adjust函数的比较逻辑

仿函数本质是一个类,可以通过模版参数进行传递,默认传的为less,控制它为大堆

template<class T, class Container = vector<T>, class Compare = less<T>>
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])//这个_con可以是less,也可以是greater,实现比较if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

 com是Compare的对象,它的对象可以像函数一样使用

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])//这个_con可以是less,也可以是greater,实现比较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;}}
}

对于自定义类型的其他仿函数使用 

 如果在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 test()
{priority_queue<Date, vector<Date>, greater<Date>> pq;Date d1(2024, 4, 8);pq.push(d1);pq.push(Date(2024, 4, 10));pq.push({ 2024, 2, 15 });while (!pq.empty()){cout << pq.top() << " ";pq.pop();}cout << endl;
}

 

这是因为调用greater这个仿函数的时候,去比较,涉及自定义类型的比较,就会去调用Date类里面的>运算符重载。

再看下面这个:我如果存的是指针呢?

void test1()
{myown::priority_queue<Date*, vector<Date*>, myown::greater<Date*>> pqptr;pqptr.push(new Date(2024, 4, 14));pqptr.push(new Date(2024, 4, 11));pqptr.push(new Date(2024, 4, 15));while (!pqptr.empty()){cout << *(pqptr.top()) << " ";pqptr.pop();}cout << endl;
}

数据就不对了!是因为我们是比较指针的地址,而不是指针指向的内容。

所有我们应该 自己构造一个仿函数:

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

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

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

相关文章

Java并发编程:Thread原理解析和协程介绍

文章目录 一、java中的thread和操作系统中的Thread对应关系二、协程 一、java中的thread和操作系统中的Thread对应关系 在java中用户线程和内核线程是1:1的形式&#xff1a; 其中java层面创建的线程为用户线程&#xff0c;其对应的底层线程为内核线程。 Java生成线程的流程如…

使用GitLab自带的CI/CD功能在本地部署.Net8项目(二)

前置内容&#xff1a; 通过Docker Compose部署GitLab和GitLab Runner&#xff08;一&#xff09; 目录 一、创建代码仓库 二、创建GitLabRunner 三、注册Runner 四、配置Runner&#xff0c;绑定宿主Docker 五、创建.Net8WebApi项目进行测试 六、总结 一、创建代码仓库 …

【Redis7】10大数据类型之Stream类型

文章目录 1. Stream简介2. 生产消息命令(XADD)3. 查询相关命令3.1 获取指定范围内的消息(XRANGE)3.2 逆序获取指定范围内的消息(XREVRANGE)3.3 返回消息的数量(XLEN) 4. 删除消息命令(XDEL)5. 截取消息命令(XTRIM)6. 消费消息命令(XREAD)7. 消费者组管理命令7.1 创建消费者组(X…

考研数学|24像张宇那样的题?李林880和李永乐660不够用了?

以前的卷子就不说了&#xff0c;就说说最近的24年的考研数学题 24年考研数学真题评价&#xff1a; 首先数学二在计算量上超过了数学三&#xff0c;尤其是在高等数学的选择题部分&#xff0c;这使得数学二的难度可能略高于数学三&#xff0c;尽管两者之间并没有本质的差异。与…

【基础绘图】 10.饼图

效果图&#xff1a; 主要步骤&#xff1a; 1. 数据准备&#xff1a;自己赋值的随机数 2. 图像绘制&#xff1a;绘制饼图 详细代码&#xff1a;着急的直接拖到最后有完整代码 步骤一&#xff1a;导入库包及图片存储路径并设置中文字体为宋体&#xff0c;西文为新罗马&#…

java项目之车辆管理系统(springboot+vue+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的车辆管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 车辆管理系统的主要使用者分…

【Unity UI系统介绍】

Unity UI系统介绍 想了解更多游戏开发知识,可以扫描下方二维码,免费领取游戏开发4天训练营课程 Unity UI 是 Unity 引擎中的一套用户界面&#xff08;UI&#xff09;系统&#xff0c;允许开发者创建和管理游戏的用户界面。 Canvas&#xff1a;Canvas 是 Unity UI 的核心组件…

零基础又怎样?教你这样高效拿下HCIE!

HCIE认证是网络技术领域内权威的认证之一&#xff0c;对于零基础的学员来说&#xff0c;这似乎是一项几乎不可能完成的任务&#xff0c;很多人想转行都望而却步。 然而&#xff0c;只要策略得当&#xff0c;零基础的学员同样可以高效地迈向HCIE的殿堂。 今天就聊聊&#xff0…

CAD绘制3维场景图记录

文章目录 1.给三维体每个面上不同的颜色1.1 软件设置1.2 着色面 2.在长方体上画圆柱体 1.给三维体每个面上不同的颜色 1.1 软件设置 这里必须改为真实&#xff0c;否则之后的面上色只能显示更改了线条颜色 1.2 着色面 如果你菜单栏没有显示&#xff0c;在上面小倒三角那里…

windows@注册表介绍@注册表的查看和编辑操作

文章目录 abstractrefs注册表的主要组件包括根键极其缩写名称&#x1f47a;子键特性 查看注册表&#x1f47a;使用powershell查看路径下的子路径声明概念Get-ChildItem查看注册表路径下的项Set-Location进入注册表路径举例说明查看文件系统某个路径下的项查看某个注册表路径的项…

图和网络笔记

文章目录 1. A X 0 AX0 AX02. A T Y 0 A^TY0 ATY03. A X 0 AX0 AX0和 A T Y 0 A^TY0 ATY0的关系 1. A X 0 AX0 AX0 一个图可以由节点和边组成&#xff0c;假设我们有一个节点notes &#xff1a;n4,边edges&#xff1a;m5的有向图&#xff0c;表示如下 通过以上电路…

Pycharm使用Anaconda虚拟环境

一、前置 安装 Pychram安装 Anaconda&#xff0c;并配置虚拟环境 参考&#xff1a; Anaconda虚拟环境 anaconda虚拟环境pytorch安装 二、在Pycharm中使用Anaconda的虚拟环境 打开 Pycharm的命令行可以看到 Anaconda 的虚拟环境已经启动。 三、问题集合 &#xff08;1&…

多模态产品在智能文档处理应用的展望------以TextIn模型为例

前言发展现状TextIn 文档解析技术文本向量化展望合合信息 前言 第十四届视觉与学习青年学者研讨会(VALSE 2024)于5月5日-7日在山城重庆渝北区悦来国际会议中心举办。大会聚焦计算机视觉、模式识别、多媒体和机器学习等领域的国际前沿和热点方向。大会中&#xff0c;合合信息智能…

限流算法(令牌桶漏桶计数器)

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;Spring⛺️稳中求进&#xff0c;晒太阳 业务重的三种情况&#xff1a;突发流量、恶意流量、业务本身需要 限流: 是为了保护自身系统和下游系统不被高并发流量冲垮&#xff0c;导致系统雪崩…

数据中心--AI时代的“炼油厂”

数据中心正在成为AI时代的“炼油厂”&#xff01; 众所周知&#xff0c;AI的高歌猛进催生了对数据的海量处理需求。为了满足蓬勃的算力需求&#xff0c;全球开启了新一轮的数据中心建设热潮&#xff0c;数据中心业务正在以指数级的速度疯狂扩张。 此番情景&#xff0c;和第二…

Git系列:git grep 被忽视的操作细节

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

java面试题(常见集合)

算法复杂度分析 时间复杂度分析 时间复杂度分析&#xff1a;来评估代码的执行耗时的 大O表示法&#xff1a;不具体表示代码真正的执行时间&#xff0c;而是表示代码执行时间随数据规模增长的变化趋势 空间复杂度 空间复杂度的全称是渐进空间复杂度&#xff0c;表示算法占用…

webpack5以下的项目,前端引入node的path模块需要额外配置

webpack5以下的项目&#xff0c;前端import * as path from path时需要额外配置&#xff0c;这里以vue.config.js为例 刚开始引入时报错 其实就是在打包前端项目的时候&#xff0c;将path模块替换成 path-browserify 模块&#xff0c;所以还需要安装 path-browserfify 模块 …

【Linux】磁盘文件

思维导图 学习目标 了解磁盘的物理结构和存储结构&#xff0c;并将其存储结构进行抽象&#xff01;&#xff01; 一、了解一下磁盘及其物理结构 1.1 计算机只认识二进制 什么是二进制&#xff1f;&#xff1f;0&#xff0c;1是被规定出来的&#xff0c;在计算机里面我们用高低…

Excel Module: Iteration #1 EasyExcel生成下拉列表模版时传入动态参数查询下拉数据

系列文章 EasyExcel生成带下拉列表或多级级联列表的Excel模版自定义校验导入数据(修订) 目录 系列文章前言仓库一、实现1.1 下拉元数据对象1.2 构建下拉元数据的映射关系1.3 框架方式1.3.1 框架实现1.3.2 框架用例模版类加载下拉业务导出接口 1.4 EasyExcel方式1.4.1 EasyExce…