【C++】priority_queue仿函数
今天我们来学习C++中另一个容器适配器:优先级队列——priority_queue;和C++一个重要组件仿函数:
目录
一、priority_queue
1.1 priority_queue是什么
1.2 priority_queue的接口
1.2.1 priority_queue使用举例
二、仿函数
三、关于priority_queue的例题
四、模拟实现priority_queue
五、priority_queue的使用拓展
一、priority_queue
1.1 priority_queue是什么
priority_queue介绍文档:priority_queue - C++ Reference (cplusplus.com)
优先队列(priority_queue)是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
其底层就是堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。对于堆不熟悉的可以看到这里:【精选】【数据结构初阶】树与二叉树——堆
优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类(默认使用vector)。容器应该可以通过随机访问迭代器访问,并支持以下操作: empty():检测容器是否为空、size():返回容器中有效元素个数 、front():返回容器中第一个元素的引用、push_back():在容器尾部插入元素、pop_back():删除容器尾部元素
标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector
需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数 make_heap、push_heap和pop_heap来自动完成此操作
1.2 priority_queue的接口
| 函数声明 | 接口说明 |
|---|---|
| priority queue()/priority queue(first,last) | 构造一个空的优先级队列() |
| empty | 检测优先级队列是否为空,是返回true,否则返回 false |
| size | 返回优先级队列中元素总个数 |
| top | 返回优先级队列中最大(最小元素),即堆顶元素 |
| push | 在优先级队列中插入元素 |
| pop | 删除优先级队列中最大(最小)元素,即堆顶元素 |
1.2.1 priority_queue使用举例
#include<iostream>
#include<vector>
#include<queue>
#include<functional>using namespace std;
int main()
{priority_queue<int> q1;//大堆q1.push(5);q1.push(1);q1.push(10);q1.push(8);q1.push(7);while (!q1.empty()){cout<<q1.top()<<" ";q1.pop();}cout << endl;priority_queue<int,vector<int>,greater<int>> q2;//小堆q2.push(5);q2.push(1);q2.push(10);q2.push(8);q2.push(7);while (!q2.empty()){cout << q2.top() << " ";q2.pop();}return 0;
}
运行效果:

我们可以看到构建一个小堆要用到仿函数,下面我们来看看仿函数是个什么东西:
二、仿函数
我们先来看到下面这段代码:
struct ADD
{int operator()(int x, int y){return x + y;}
};
int main()
{struct ADD add;cout << add(1,9) << endl;return 0;
}
运行效果:

我们可以看到,该段代码在结构体中实现了一个()的运算符重载,接着使用该结构体定义了一个对象add,用该对象调用里面的运算符重载函数实现了一个功能,这就是大名鼎鼎的仿函数
由此看来仿函数的本质就是()的运算符重载,使创建的对象可以像函数一样使用
三、关于priority_queue的例题
题目地址:
215. 数组中的第K个最大元素
解题思路:对于该使一个经典的top-k问题,对于题我们可以先取k个数建立一个小堆,再将后面的数依次与堆顶元素相比较,如果比堆顶元素大就将堆顶元素丢弃后,将该数入堆;接着再接着与下一个数相比,这样最终堆顶元素就是第K个最大的数了:
解题代码:
class Solution {
public:int findKthLargest(vector<int>& nums, int k) {priority_queue<int,vector<int>,greater<int>> q(nums.begin(),nums.begin()+k);for(int i=k;i<nums.size();++i){if(nums[i]>q.top()){q.pop();q.push(nums[i]);}}return q.top();}
};

四、模拟实现priority_queue
下面我们来手搓一个priority_queue:
#pragma once
#include<iostream>
#include<vector>
#include<algorithm>namespace lhs
{template<class T, class container = std::vector<T>>class priority_queue{private:void adjustup(size_t child)//向上调整{while (child > 0){size_t parent = (child - 1) / 2;if (_con[child] > _con[parent]){std::swap(_con[child], _con[parent]);child = parent;}else break;}}void adjustdown(size_t parent)//向下调整{size_t child = parent * 2 + 1;while (child < size()){if (child + 1 < size() && _con[child] < _con[child + 1]){++child;}if (_con[parent] < _con[child]){std::swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else break;}}public:void push(const T& x){_con.push_back(x);adjustup(size() - 1);}void pop(){std::swap(_con[0], _con[size() - 1]);_con.pop_back();adjustdown(0);}size_t size(){return _con.size();}bool empty(){return _con.empty();}const T& top(){return _con[0];}private:container _con;};
}
测试代码:
int main()
{lhs::priority_queue<int> q;q.push(1);q.push(2);q.push(3);q.push(4);q.push(10);q.push(18);while (!q.empty()){std::cout << q.top() << " ";q.pop();}std::cout << std::endl;return 0;
}
运行效果:

但是上面手搓的priority_queue只能实现大堆的效果,要实现小堆怎么办?我们不可能手动修改代码中向上和向下调整函数中的判断符吧?
这里就要轮到仿函数登场了:
#pragma once
#include<iostream>
#include<vector>
#include<algorithm>namespace lhs
{template<class T>struct less{bool operator()(const T& x, const T& y){return x > y;}};template<class T>struct greater{bool operator()(const T& x, const T& y){return x < y;}};template<class T, class container = std::vector<T>, class compare = less<T>>class priority_queue{private:void adjustup(size_t child)//向上调整{compare com;while (child > 0){size_t parent = (child - 1) / 2;if (com(_con[child], _con[parent])){std::swap(_con[child], _con[parent]);child = parent;}else break;}}void adjustdown(size_t parent)//向下调整{compare com;size_t child = parent * 2 + 1;while (child < size()){if (child + 1 < size() && com(_con[child + 1], _con[child])){++child;}if (com(_con[child], _con[parent])){std::swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else break;}}public:void push(const T& x){_con.push_back(x);adjustup(size() - 1);}void pop(){std::swap(_con[0], _con[size() - 1]);_con.pop_back();adjustdown(0);}size_t size(){return _con.size();}bool empty(){return _con.empty();}const T& top(){return _con[0];}private:container _con;};
}
我们可以看到,我们上面定义了两个仿函数:less(构建大堆)和greater(构建小堆),我们在测试代码中传入我们所要选择的仿函数类型来控制大小堆的构建(本质是通过不一样的仿函来控制运算符的改变):
int main()
{//lhs::priority_queue<int> q;//大堆lhs::priority_queue<int, std::deque<int>, lhs::greater<int>> q;//小堆q.push(1);q.push(2);q.push(3);q.push(4);q.push(10);q.push(18);while (!q.empty()){std::cout << q.top() << " ";q.pop();}std::cout << std::endl;return 0;
}
运行效果:

五、priority_queue的使用拓展
我们把之前写的日期类拿出来:
class Date
{friend ostream& operator<<(ostream& out, const Date& d);
public:Date(int year, int month, int day);//构造函数int GetMonthDay(int year, int month) const;//获取year年month月的天数//运算符重载bool operator==(const Date& d) const;//判断日期是否相同bool operator!=(const Date& d) const;//判断日期是否不相同bool operator<(const Date& d) const;//判断当前日期是否在传入日期之前bool operator<=(const Date& d) const;//判断当前日期是否在传入日期之前或相同bool operator>(const Date& d) const;//判断当前日期是否在传入日期之后bool operator>=(const Date& d) const;//判断当前日期是否在传入日期之后或相同private:int _year;int _month;int _day;
};int Date::GetMonthDay(int year, int month) const
{int MonthDay[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };//用数组来依次存储平年1到12月的天数if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))//判断是否为闰年的2月{return 29;}return MonthDay[month - 1];
}
Date::Date(int year, int month, int day)
{if (month < 1 || month>12 || (day<1 || day>GetMonthDay(year, month)))//判断日期是否合法{cout << "Illegal date!" << endl;}else{_year = year;_month = month;_day = day;}
}
bool Date::operator==(const Date& d) const
{return _year == d._year && _month == d._month && _day == d._day;
}
bool Date::operator!=(const Date& d) const
{return !(*this == d);
}
bool Date::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 Date::operator<=(const Date& d) const
{return (*this < d) || (*this == d);
}
bool Date::operator>(const Date& d) const
{return !(*this <= d);
}
bool Date::operator>=(const Date& d) const
{return !(*this < d);
}ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "/" << d._month << "/" << d._day << endl;return out;
}
下面我们可以使用priority_queue对自己写的日期类进行存储:
int main()
{Date d1(2022, 10, 23);Date d2(2022, 10, 25);Date d3(2022, 10, 24);priority_queue<Date> q1;q1.push(d1);q1.push(d2);q1.push(d3);cout << q1.top() << endl;priority_queue<Date,vector<Date>, greater<Date>> q2;q2.push(d1);q2.push(d2);q2.push(d3);cout << q2.top() << endl;return 0;
}
运行效果:

因为我们自实现的Date重载了<和>运算符,所以在我们使用priority_queue来存储时,其内部会自动调用我们所写的运算符重载来构建大小堆
再看到下面的代码:
int main()
{priority_queue<Date*> q1;q1.push(new Date(2022, 10, 23));q1.push(new Date (2022, 10, 25));q1.push(new Date (2022, 10, 24));cout << *q1.top() << endl;return 0;
}
多次运行效果:

咦?怎么每次运行的结果都不一样?
仔细看,这里我们传入的存储类型是Date*,而对于指针类型,按默认的<和>运算符来比较这是比较指针所指向地址空间的大小,但new创建空间时地址是随机的,所以这是不合理的
下面我们来写两个仿函数实现一下Date*类型的比较:
class Date_less
{
public:bool operator()(Date*a, Date* b){return *a < * b;}
};class Date_greater
{
public:bool operator()(Date* a, Date* b){return *a > *b;}
};
int main()
{priority_queue<Date*,vector<Date*>,Date_less> q1;priority_queue<Date*, vector<Date*>, Date_greater> q2;q1.push(new Date(2022, 10, 23));q1.push(new Date (2022, 10, 25));q1.push(new Date (2022, 10, 24));cout << *q1.top() << endl;q2.push(new Date(2022, 10, 23));q2.push(new Date(2022, 10, 25));q2.push(new Date(2022, 10, 24));cout << *q2.top() << endl;return 0;
}
在使用priority_queue时传入我们所写的仿函数就可以实现Date*类型的大小堆存储啦
综上所述,泛型和运算符重载为我们提供了极大的方便,我们如果对系统所提供的东西不满意,就可以使用自己所定义的方法,一切都自在掌控~
相关文章:
【C++】priority_queue仿函数
今天我们来学习C中另一个容器适配器:优先级队列——priority_queue;和C一个重要组件仿函数: 目录 一、priority_queue 1.1 priority_queue是什么 1.2 priority_queue的接口 1.2.1 priority_queue使用举例 二、仿函数 三、关于priority…...
如何驾驭ChatGPT:掌控有效对话!
📢📢📢📣📣📣 哈喽!大家好,我是【一心同学】,一位上进心十足的【后端领域博主】!😜😜😜 ✨【一心同学】的写作风格&#x…...
LeetCode 面试题 16.03. 交点
文章目录 一、题目二、C# 题解 一、题目 给定两条线段(表示为起点 start {X1, Y1} 和终点 end {X2, Y2}),如果它们有交点,请计算其交点,没有交点则返回空值。 要求浮点型误差不超过 10^-6。若有多个交点(…...
【码银送书第九期】《ChatGPT 驱动软件开发:AI 在软件研发全流程中的革新与实践》
计算机技术的发展和互联网的普及,使信息处理和传输变得更加高效,极大地改变了金融、商业、教育、娱乐等领域的运作方式。数据分析、人工智能和云计算等新兴技术,也在不断地影响和改变着各个行业。 如今,我们正在见证人工智能技术的…...
Hadoop3.0大数据处理学习4(案例:数据清洗、数据指标统计、任务脚本封装、Sqoop导出Mysql)
案例需求分析 直播公司每日都会产生海量的直播数据,为了更好地服务主播与用户,提高直播质量与用户粘性,往往会对大量的数据进行分析与统计,从中挖掘商业价值,我们将通过一个实战案例,来使用Hadoop技术来实…...
华为机试题:HJ3 明明的随机数
目录 第一章、算法题1.1)题目描述1.2)解题思路与答案1.3)牛客链接 友情提醒: 先看文章目录,大致了解文章知识点结构,点击文章目录可直接跳转到文章指定位置。 第一章、算法题 1.1)题目描述 题目描述&…...
Python OpenCV将n×n的小图拼接成m×m的大图
Python OpenCV将nn的小图拼接成mm的大图 前言前提条件相关介绍实验环境n \times n的小图拼接成m \times m的大图代码实现 前言 由于本人水平有限,难免出现错漏,敬请批评改正。更多精彩内容,可点击进入Python日常小操作专栏、OpenCV-Python小…...
wkhtmltoimage/wkhtmltopdf 使用实践
1. 介绍 wkhtmltopdf/wkhtmltoimage 用于将简单的html页面转换为pdf或图片; 2.安装 downloads 2.1. mac os 下载64-bit 版本然后按照指示安装, 遇到 untrust developers 时,需要在 Settings -> Privacy 处信任下该安装包。 2.2. debian # 可用…...
Rclone连接Onedrive
一、Rclone介绍 Rclone是一款的命令行工具,支持在不同对象存储、网盘间同步、上传、下载数据。 我们这里连接的onedrive,其他网盘请查看官方文档。 注意: 需要先在Windows下配置好了,然后再将rclone配置文件复制到Linux的rclone配…...
RK356X/RK3588构建Ubuntu20.04根文件系统
文章目录 前言一、官网下载ubuntu-base二、挂载并构建文件系统2.1、配置构建文件系统环境2.2、编写挂载脚本mount.sh并安装相关工具2.3、轻量级的桌面环境 lubuntu-desktop2.4、卸载一些不必要的软件2.5、添加用户2.6 、允许root用户登录桌面2.7、串口自动登录2.8、添加分区释放…...
本地新建项目如何推到码云上去
1.先在码云上建立一个空仓库,正常步骤就行。建立完成有readme.md. 2.然后本地建立项目文件,正常脚手架搭建VUE\REACT等。记得要项目git init一下。 3.本地改好的内容commit 一下。 4.本地文件与远端仓库建立连接。git remote add origin https://gite…...
RSAUtil 前端 JavaScript JSEncrypt 实现 RSA (长文本)加密解密
文章归档:https://www.yuque.com/u27599042/coding_star/cl4dl599pdmtllw1 依赖 import JSEncrypt from ‘jsencrypt’ pnpm i jsencryptimport {stringIsNull} from “/utils/string_utils.js”:https://www.yuque.com/u27599042/coding_star/slncupw…...
uniapp map polygons 区域填充色(fillColor)在ios显示正常,但在安卓手机显示是黑色的,怎么解决?
uniapp map polygons 区域填充色(fillColor)在ios显示正常,但在安卓手机显示是黑色的,怎么解决? <MapPage :longitude"item.centerCoord[0]" :latitude"item.centerCoord[1]":polygons"[{ points: it…...
OSCAR数据库上锁问题如何排查
关键字 oscar lock 问题描述 oscar 数据库上锁问题如何排查 解决问题思路 准备数据 create table lock_test(name varchar(10),age varchar(10));insert into lock_test values(ff,10); insert into lock_test values(yy,20); insert into lock_test values(ll,30);sessio…...
FPGA与人工智能泛谈-01
文章目录 前言一、FPGA(Field Programmable Gate Array)是什么?二、与GPU的对比1.GPU特点2. FPGA的优势三、人工智能实现的基础架构总结前言 人工智能技术的快速发展正从各个方面改变人类的生活、工作及教育等各个方面,其中人工智能算法的演进又是其中的关键一步,其中会涉及…...
【VASP】POTCAR文件
【VASP】POTCAR文件 POTCAR 文件的介绍qvasp 生成POTCARvaspkit 生成POTCAR再来认识一下各种赝势如何区分US、PAW、LDA、GGA、PW91 前言 一、4个常用的输入文件INCAR、POSCAR、POTCAR、KPOINTS INCAR: 计算任务类型是什么?怎么计算? KPOINTS: 包含了倒易…...
棒球俱乐部青少年成长体系·棒球1号位
棒球俱乐部青少年成长体系介绍 1. 培养理念 简要介绍棒球俱乐部的宗旨和培养青少年的目标 棒球俱乐部是一个致力于培养青少年棒球运动员的体育组织,其宗旨是通过提供专业的棒球训练和比赛机会,帮助青少年提高身体素质、培养团队合作精神和塑造积极向上…...
折叠式菜单怎么做编程,初学编程系统化教程初级1上线
中文编程系统化教程,不需英语基础,学习链接——入门篇课程 https://edu.csdn.net/course/detail/39036中文编程系统化教程,不需英语基础,学习链接—— 初级1课程 https://edu.csdn.net/course/detail/39061 ——————————…...
与AI对话,如何写好prompt?
玩转AIGC,优质的Prompt提示词实在是太重要了!同样的问题,换一个问法,就会得到差别迥异的答案。你是怎样和AI进行对话交流的呢?我来分享几个: 请告诉我…我想知道…对于…你有什么看法?帮我解决…...
基于YOLOv8模型和UA-DETRAC数据集的车辆目标检测系统(PyTorch+Pyside6+YOLOv8模型)
摘要:基于YOLOv8模型和UA-DETRAC数据集的车辆目标检测系统可用于日常生活中检测与定位汽车(car)、公共汽车(bus)、面包车(vans)等目标,利用深度学习算法可实现图片、视频、摄像头等方…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
