【C++】priority_queue仿函数

news2024/11/15 13:37:47

今天我们来学习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*类型的大小堆存储啦

综上所述,泛型和运算符重载为我们提供了极大的方便,我们如果对系统所提供的东西不满意,就可以使用自己所定义的方法,一切都自在掌控~

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

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

相关文章

Linux C语言开发-D7D8运算符

算术运算符&#xff1a;-*/%&#xff0c;浮点数可以参与除法运算&#xff0c;但不能参与取余运算 a%b&#xff1a;表示取模或取余 关系运算符&#xff1a;<,>,>,<,,! 逻辑运算符:!,&&,|| &&,||逻辑运算符是从左到右&#xff0c;依次运算&#…

freeRTOS内部机制——栈的作用

上图中*pa 和*pb分别为R0&#xff0c;R1&#xff0c;调用C函数时&#xff0c;第一个参数保存在R0中第二个参数保存在R1中。这是约定。 指令保存在哪里&#xff1f; 指令保存在flash上面 LR等于什么? LR是返回地址&#xff0c;函数执行完了过后LR等于下一条指令的地址 运行…

JDK8新特性:Stream流

目录 1.获取Stream流 2.Stream流常见的中间方法 3.Stream流常见的终结方法 1、 Stream 是什么&#xff1f;有什么作用&#xff1f;结合了什么技术&#xff1f; ●也叫 Stream 流&#xff0c;是Jdk8开始新增的一套 API ( java . util . stream .*)&#xff0c;可以用于操作集…

【JAVA学习笔记】38 - 单例设计模式-静态方法和属性的经典使用

项目代码 https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_Chapter10/src/com/yinhai/final_ 一、什么是设计模式 1.静态方法和属性的经典使用 2.设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式。设计模式就像是…

【完美世界】被骂国漫之耻,石昊人设战力全崩,现在真成恋爱世界了

【侵权联系删除】【文/郑尔巴金】 深度爆料&#xff0c;《完美世界》动漫第135集预告片已经更新了&#xff0c;但是网友们对此却是一脸槽点。从预告中可以看出&#xff0c;石昊在和战王战天歌的大战中被打成重伤&#xff0c;最后云曦也被战天歌抓住。在云曦面临生死危机的时候…

AIGC底层数据探索——高质量数据助力大模型迭代升级

// 编者按&#xff1a;近年来&#xff0c;大模型的概念逐渐受到更广泛的关注&#xff0c;而谈及大模型就离不开对底层数据的探索。 大模型训练数据痛点与中文数据集现状&#xff1b;高质量数据定义&#xff1b;对话式数据模型实验&#xff1b;晴数智慧高质量数据解决方案。 文…

信号补零对信号频谱的影响

文章目录 前言一、 什么是补零二、案例三、补零前仿真及分析1、补零前 MATLAB 源码2、仿真及结果分析①、 x n x_n xn​ 时域图②、 x n x_n xn​ 频谱图 四、补零后仿真及分析1、补6000个零且1000采样点①、 MATLAB 源码②、仿真及结果分析 2、波形分辨率3、补6000个零且7000采…

电子巡更和智能巡检关系

电子巡更和智能巡检是两种重要的安全巡查技术&#xff0c;它们之间相似相通。 电子巡更是一种基于传统巡更系统发展而来的技术&#xff0c;主要通过数字化手段对巡查工作进行记录和监督。它通常由巡更棒、信息钮和电子巡更软件组成。巡查人员在进行巡查时&#xff0c;需要携带…

Kafka集群搭建与SpringBoot项目集成

本篇文章的目的是帮助Kafka初学者快速搭建一个Kafka集群&#xff0c;以及怎么在SpringBoot项目中使用Kafka。 kafka集群环境包地址&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;x9yn 一、Kafka集群搭建 1、准备环境 &#xff08;1&#xff09;准备三台…

泵站机电设备健康状态系统建立的关键

在现代工业运营中&#xff0c;泵站机电设备的健康管理至关重要。通过建立一套完善的泵站机电设备健康管理系统&#xff0c;可以有效地监测、诊断和维护设备&#xff0c;确保其正常运行和延长使用寿命。本文将从三个方面展开讨论&#xff0c;分别是泵站机电设备养护在设备健康管…

题目 1053: 二级C语言-平均值计算(python详解)——练气三层初期

✨博主&#xff1a;命运之光 &#x1f984;专栏&#xff1a;算法修炼之练气篇&#xff08;C\C版&#xff09; &#x1f353;专栏&#xff1a;算法修炼之筑基篇&#xff08;C\C版&#xff09; &#x1f352;专栏&#xff1a;算法修炼之练气篇&#xff08;Python版&#xff09; ✨…

不懂项目管理三角,你的项目很难成功

在管理项目时&#xff0c;难免会出现影响项目的变更或其他问题。为了防止项目超出计划或超支&#xff0c;项目经理总是要平衡项目管理三角形&#xff08;由三个主要项目约束组成&#xff09;。 什么是项目管理三角形&#xff1f; 项目管理三角形由决定项目质量的三个约束组成…

LAMP项目部署实战

一、LAMP环境部署 1、回顾LAMP LAMP Linux Apache MySQL PHP Apache&#xff1a;主要用于接收用户的请求&#xff0c;处理业务逻辑&#xff0c;返回结果给客户端&#xff08;浏览器&#xff09; PHP&#xff1a;编程语言的一种&#xff0c;主要应用于Web开发。主要实现注…

python输出与数据类型

目标 1、使用print输出内容 2、熟悉字符串类型 3、熟悉数字类型 4、熟悉数字与字符串操作 输出 print可控制输出内容也可配合、-、*、/进行运算&#xff0c;和整数型配合可进行运算和字符型配合有不同效果&#xff0c;如为拼接&#xff0c;*为多次输出注&#xff1a;整数型如&…

【ROS入门】机器人系统仿真——URDF集成Gazebo

文章结构 URDF与Gazebo基本集成流程创建功能包编写URDF或Xacro文件启动 Gazebo 并显示机器人模型 URDF集成Gazebo相关设置collisioninertial颜色设置 URDF集成Gazebo实操编写封装惯性矩阵算法的 xacro 文件复制相关 xacro 文件&#xff0c;并设置 collision inertial 以及 colo…

嵌入式中的MCU、ARM、DSP、FPGA

目录 “角色扮演” MCU ARM 特点 DSP 特点 FPGA 特点 应用 “角色扮演” MCU&#xff08;Microcontroller Unit&#xff09;、ARM&#xff08;Advanced RISC Machine&#xff09;、DSP&#xff08;Digital Signal Processor&#xff09;和FPGA&#xff08;Field-Progr…

【Java 进阶篇】Java Servlet 执行原理详解

Java Servlet 是用于构建动态Web应用程序的关键组件之一。它允许开发者编写Java类来处理HTTP请求和生成HTTP响应&#xff0c;从而实现灵活、交互性强的Web应用。本篇博客将深入探讨Java Servlet的执行原理&#xff0c;适用于初学者&#xff0c;无需太多的先验知识。 什么是 Ja…

Elasticsearch:使用 Open AI 和 Langchain 的 RAG - Retrieval Augmented Generation (四)

这篇博客是之前文章&#xff1a; Elasticsearch&#xff1a;使用 Open AI 和 Langchain 的 RAG - Retrieval Augmented Generation &#xff08;一&#xff09;Elasticsearch&#xff1a;使用 Open AI 和 Langchain 的 RAG - Retrieval Augmented Generation &#xff08;二&a…

解决Linux下编译Intel oneTBB动态库出错的问题

在CMakeLists.txt中&#xff0c;原来有一段这样查找和链接的配置代码 find_library(tbblibaray ${tbb_path}) target_link_libraries(backalarm ${tbblibaray})编译后提示错误&#xff1a; /myapp/library/tbb/libtbb.so&#xff1a;对‘__cxa_throw_bad_array_new_lengthCX…

MATLAB源码-第55期】matlab代码基于m序列的多用户跳频通信系统仿真,输出各节点波形图。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 1.跳频扩频调制 跳频扩频调制通过伪随机地改变发送载波频率&#xff0c;用跳变的频率来调制基带信号&#xff0c;得到载波频率不断变化的射频信号。 通常&#xff0c;跳频系统的频率合成器输出什么频率的载波信号是受跳频指…