初识C++ · 优先级队列

目录

前言:

1 优先级队列的使用

2 优先级队列的实现

3 仿函数


前言:

栈和队列相对其他容器来说是比较简单的,在stl里面,有一种容器适配器是优先级队列(priority_queue),它也是个队列,但是不大像队列,本文中简略介绍如何使用和模拟实现它。


1 优先级队列的使用

要使用,先文档:

文档黑体第一句话就是,优先级队列是一种容器配置器,容器配置器是?电脑有电源适配器,起到提供电源的作用,但是这个适配器不是充电器,即充电的时候,电源来自于电线,但是我们不能直接拿电线充电吧?这时候适配器就有用了,通过封装,使得两边达到一个配合的效果,容器配置器同理。当然这不是重点。

模板是必要的,模板参数有3个,参考栈和队列,第二个参数是底层用的哪种容器进行实现的,这里的默认是使用的顺序表,第三个参数是仿函数,后面介绍。

那么在来看看成员函数:

很少是吧,可以说成员函数和队列没有什么差别,所以我们现在简单使用一下:

int main()
{
	priority_queue<int> q1;
	q1.push(2);
	q1.push(1);
	q1.push(4);
	q1.push(3);
	q1.push(5);
	cout << q1.size() << endl;

	while (!q1.empty())
	{
		cout << q1.top() << " ";
		q1.pop();
	}
	cout << endl;
	cout << q1.size() << endl;
	return 0;
}

使用很简单,但是打印的结果却,,有点奇怪?

这就是优先级队列的特殊之处了,我们并没有对它进行排序,但是打印出来是默认有序的,这是因为它的本质是堆,而模板参数第三个仿函数的参与,决定了它是大堆还是小堆,默认是升序的,可以理解为升序状态下谁最小谁优先级最高,所以先被打印。

但是实际上,不用管那么多,就把它认为是堆就ok了,所以我们实现的时候需要实现向上调整算法和向下调整算法。

对于仿函数我们放在实现了80%在介绍。


2 优先级队列的实现

我们需要实现以下几个接口:

empty,size,push,pop,top,接口不多,另外还要实现向上调整算法和向下调整算法。

优先级队列的一般结构为:

template <class T,class container = vector<T>>
class priority_queue
{
public:

private:
	container _con;

};

模板的第三个参数先不急。

简单的几个接口我们一股脑实现就完事了:

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

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

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

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

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

这部分是没什么难度的。

随机要实现的是push和pop,这里提问,为什么默认的底层实现是vector呢?

因为堆的本质,就是一块连续的空间,我们只是把它抽象为了完全二叉树。

push数据之后,堆本身的结构被破坏了,所以我们需要向上调整数据,在数据结构初级的时候,已经介绍过两种算法,这里过一下就Ok:

向上调整算法是通过孩子节点和父节点的值进行比较,然后交换位置,可能有点倒反天罡,谁的值大谁就当爸爸?我们知道孩子节点的下标之后,父节点的下标就是(孩子下标 - 1) / 2,如果不熟悉可以拿图推演一下,最后一次交换的时候,孩子节点的下标变成了父节点的下标,所以判断条件应该是孩子节点的下标是否合法,即是否大于0:

void adjust_up(size_t child)
{
	size_t parent = (child - 1) / 2;
	while (child > 0)
	{
		if(_con[parent] < _con[child])
		{
			swap(_con[parent],_con[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

此时push的的准备工作就做好了,插入,调整,完成:

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

与之对应的是pop,pop对应的准备工作是向下调整算法,向下调整算法有几个需要注意的点就是,我们建大堆,那么就要在孩子节点中找到两个孩子中较大的那一个,除此之后,不是所有的节点都有两个子节点,所以我们需要保证孩子 + 1不小于size,这是基础工作,随机就是比较大小,谁大谁就往上:

void adjust_down(size_t parent)
{
	size_t child = parent * 2 + 1;
	while (child < _con.size())
	{
		if (child + 1 < _con.size() && _con[child] < _con[child + 1])
		{
			child++;
		}
		if (_con[child] > _con[parent])
		{
			swap(_con[child], _con[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}

pop的准备工作也是完毕了,那么pop就行,对于堆部分有些遗忘的同学就会想为什么涉及到向下调整,因为删除,为了效率和不破坏堆的结构,我们的措施是首尾交换,向下调整。

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

3 仿函数

仿函数是本文的重点,我们需要先知道什么是仿函数?

先看一段这样的代码:

template <class T>
struct Less
{
	bool operator()(const T& a,const T& b)
	{
		return a > b;
	}
};
void Func(int ,int)
{
	cout << endl;
}

int main()
{
	Less<int> Fun;
	Fun(1,2);
	Func(1,2);
	return 0;
}

如果说只看主函数的代码,就会容易觉得,这不就是两个函数调用吗?仿函数仿函数,顾名思义,模仿函数,因为调用的时候挺像函数的,但是实际上,它是一个类,这个类实例化对象,使用重载后的(),我们称这个过程叫做仿函数调用,仿函数实际上只有一个东西,就是重载的()。

重载的()没有其他东西,只有比较,对于内置类型我们可以直接比较,对于自定义类型我们可能要重载一下> 或者 <。

所以,仿函数是个类,类里面的函数是重载的(),一般是两个参数,用于比较使用。

所以我们在向上调整算法和向下调整算法的实现里面的比较,就可以用仿函数完成,进而控制升序降序,那么我们就还要写两个类:

//仿函数
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 = vector<T>,class compare = greater<T>>


void adjust_up(size_t child)
{
	compare com;
	size_t parent = (child - 1) / 2;
	while (child > 0)
	{
		//if (_con[parent] < _con[child])
		if(com(_con[parent] , _con[child]))
		{
			swap(_con[parent],_con[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void adjust_down(size_t parent)
{
	compare com;
	size_t child = parent * 2 + 1;
	while (child < _con.size())
	{
		if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
		{
			child++;
		}
		//if (_con[child] > _con[parent])
		if(com(_con[parent], _con[child]))
		{
			swap(_con[child], _con[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

向下调整算法这里有个注意的,if(child + 1 < _con.size() && com(_con[child], _con[child + 1])),第一个判断条件放在前面是最好的,因为越界了不用了比较了,代码逻辑更严密。

但是这里使用了仿函数好像也没有什么特别的,无非就是用了一个像函数调用一样的类而已,为什么不用C语言中qsort里面的函数指针呢?

在C++里面函数指针并不常用,难写不说,代码的可移植性还不高,目前我们针对的内置类型使用的仿函数效果不明显,我们引入一个日期类,一个自定义类型,进行日期的比较。

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);
	}
private:
	size_t _year;
	size_t _month;
	size_t _day;
};

对于自定义类型的比较重载已经写在了类里面,现在直接比较就可以了:

class DateGreater
{
	bool operator()(const Date& d1, const Date& d2)
	{
		return d1 > d2;
	}
};
class DateLess
{
	bool operator()(const Date& d1, const Date& d2)
	{
		return d1 < d2;
	}
};
void Test2_priority_queue()
{
	priority_queue<Date> q1;
	q1.push({ 2024, 4, 17 });
	q1.push({ 2024, 4, 18 });
	q1.push({ 2024, 4, 19 });
	q1.push({ 2024, 4, 20 });
	cout << q1.size() << endl;
	while (!q1.empty())
	{
		cout << q1.top() << " ";
		q1.pop();
	}
	cout << endl;
}

对比淘宝,淘宝的的排序,比如综合评分排序,销量排序等,用户点击对应按钮,系统就会调用对应的函数,使用的就是仿函数,函数指针在C++里面不太常用,它的类型和变量名混杂一起,看起来不太舒服。

仿函数的一般使用差不多了,但是如果我们给优先级队列里面存放日期类的指针,但是相比较日期类的大小怎么办呢?

void Test3_priority_queue()
{
	priority_queue<Date*, vector<Date*>> q1;
	q1.push(new Date(2024,5,20));
	q1.push(new Date(2024,5,21));
	q1.push(new Date(2024,5,22));
	q1.push(new Date(2024,5,23));
	while (!q1.empty())
	{
		cout << *(q1.top()) << " ";
		q1.pop();
	}
	cout << endl;
}

比如这样,里面都是指针,我们push的也是new出来的指针,我们使用的仿函数是:

class DateGreater
{
	bool operator()(const Date& d1, const Date& d2)
	{
		return d1 > d2;
	}
};
class DateLess
{
	bool operator()(const Date& d1, const Date& d2)
	{
		return d1 < d2;
	}
};

那么这个仿函数就不可以了,比较出来的都是随机结果,这是因为new出来的地址都是随机的,而默认的是比较的指针,打印出来的是对应的日期,所以结果一会儿是对的一会儿是错的,这是因为仿函数的错误使用,我们应该解引用之后再进行比较,这里就要另外提供一个仿函数了:

class PDateGreater
{
public:
	bool operator()(Date* d1, Date* d2)
	{
		return *d1 > *d2;
	}	
};

光提供了还不行,我们还要显式调用:

void Test3_priority_queue()
{
	priority_queue<Date*, vector<Date*>,PDateGreater> q1;
	q1.push(new Date(2024,5,20));
	q1.push(new Date(2024,5,21));
	q1.push(new Date(2024,5,22));
	q1.push(new Date(2024,5,23));
	while (!q1.empty())
	{
		cout << *(q1.top()) << " ";
		q1.pop();
	}
	cout << endl;
}

这样就是完美的了。仿函数的简单介绍就到这里。


感谢阅读!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

连锁门面电能监测系统是什么?

1.什么叫连锁门面电能监测系统 连锁门面电能监测系统是一种前沿的能源管理体系系统&#xff0c;针对连锁加盟店铺的电力应用情况进行实时监控及管理。这类系统根据集成化硬件配置和软件系统&#xff0c;能够帮助企业管理人员获得每个门店的电力耗费数据信息&#xff0c;进而实…

企业文件加密:数据保护的实战策略

数据是企业的生命线&#xff0c;保护数据安全就是保护企业的竞争力。在众多数据保护措施中&#xff0c;文件加密因其直接有效而备受青睐。 一、为何文件加密至关重要 在数字化办公时代&#xff0c;企业机密和敏感数据的泄露可能带来毁灭性的后果。文件加密能够确保即使数据被盗…

费效看板,YonSuite商旅费控助力企业“消灭报销”

在快速变化的商业环境中&#xff0c;差旅费用作为企业运营成本的重要组成部分&#xff0c;其管理和控制日益受到企业的重视。传统的报销流程繁琐、效率低下&#xff0c;不仅增加了企业的管理成本&#xff0c;也影响了员工的差旅体验。YonSuite商旅费控系统以其费效看板功能为核…

区间预测 | Matlab实现QRCNN-BiGRU-Attention分位数回归卷积双向门控循环单元注意力机制时序区间预测

区间预测 | Matlab实现QRCNN-BiGRU-Attention分位数回归卷积双向门控循环单元注意力机制时序区间预测 目录 区间预测 | Matlab实现QRCNN-BiGRU-Attention分位数回归卷积双向门控循环单元注意力机制时序区间预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实…

磁盘未格式化:深度解析、恢复方案及预防之道

在当今这个信息化爆炸的时代&#xff0c;磁盘未格式化问题无疑成为了众多用户头疼的难题。当我们的存储设备突然提示“磁盘未格式化”时&#xff0c;数据的丢失与恢复的挑战便摆在了我们面前。本文将深入解析磁盘未格式化的现象、原因&#xff0c;并给出两种有效的数据恢复方案…

Master-Worker 架构的灰度发布难题

作者&#xff1a;石超 一、前言 Master-Worker 架构是成熟的分布式系统设计模式&#xff0c;具有集中控制、资源利用率高、容错简单等优点。我们数据中心内的几乎所有分布式系统都采用了这样的架构。 &#xfeff; 我们曾经发生过级联故障&#xff0c;造成了整个集群范围的服…

创建 MFC DLL-使用DEF文件

本文仅供学习交流&#xff0c;严禁用于商业用途&#xff0c;如本文涉及侵权请及时联系本人将于及时删除 利用“MFC动态链接库”项目模板可以创建MFC DLL。DLL文件与可执行文件非常相似&#xff0c;不同点在于DLL包含有导出表(Export Table)。导出表包含DLL中每个导出函数的名字…

实时库存同步与并发控制:确保在线扭蛋机商品库存的实时准确性

随着电商的飞速发展&#xff0c;在线扭蛋机作为一种新兴的销售模式&#xff0c;受到了广大消费者的喜爱。然而&#xff0c;如何在大量用户同时购买时&#xff0c;确保库存信息的实时性和准确性&#xff0c;成为了摆在商家面前的一大挑战。本文将探讨如何设计一个高效且准确的库…

鸿蒙轻内核M核源码分析系列四 中断Hwi

在鸿蒙轻内核源码分析系列前几篇文章中&#xff0c;剖析了重要的数据结构。本文&#xff0c;我们讲述一下中断&#xff0c;会给读者介绍中断的概念&#xff0c;鸿蒙轻内核的中断模块的源代码。本文中所涉及的源码&#xff0c;以OpenHarmony LiteOS-M内核为例。 1、中断概念介绍…

连锁酒店水电监测管理系统

1.前言&#xff1a; 连锁酒店水电监测管理系统是当代酒店业中不可或缺智能化专用工具&#xff0c;主要是通过实时监控系统和数据分析&#xff0c;完成了对酒店电力能源所使用的精益化管理&#xff0c;减少了经营成本&#xff0c;提高了服务水平&#xff0c;并且也响应了绿色环…

centos7安装 hadoop集群

目录 准备集群搭建步骤1. 环境准备三台服务器IP关闭三台服务器的防火墙修改三台服务器的hostname文件修改三台服务器的hosts映射配置三台服务器之间的免密登录三台时间同步设置 2. hadoop安装资源划分3. 开始搭建hadoop集群192.168.83.144 即 hadoop1上的修改解压安装包添加环境…

R可视化:可发表的热图

当使用pheatmap包在R语言中实现不同组间的基因表达热图时,我们通常遵循以下步骤: 步骤 1: 加载所需的库首先,我们需要加载pheatmap包以及可能需要的其他包,如dplyr或tidyverse,用于数据预处理。 步骤 2: 准备数据我们需要一个基因表达矩阵,其中行代表基因,列代表样本,每…

非递归实现快排排序及归并排序(尾篇)

1.快速排序&#xff08;双指针实现&#xff09; 2.非递归实现快排 3.递归实现归并排序 4.非递归实现归并排序 5.总代码 1.快速排序&#xff08;双指针实现&#xff09; 俩有个指针一前一后的排放着&#xff0c;cur先走并且去找比kye对应值小的数组值&#xff0c;一旦找到后…

odoo qweb template小结

QWeb QWeb是一个基于XML的模板引擎,可用于生成HTML片段和页面。它使用XML格式来定义模板。QWeb通过在模板中添加特定的标记,来指示模板中的数据和逻辑部分。使用QWeb,你可以创建各种不同的模板,例如列表视图,表单视图和报告等。QWeb支持标准的HTML标记和控制结构,如if语…

千云物流 -openGemini生成环境使用

部署架构 安装部署 ## 创建storeclass和namespacekubectl apply -f opengemini-sc.yaml## 创建存储kubectl apply -f opengemini-stroepv.yaml #创建存储的pv&pvckubectl apply -f opengemini-metapv.yaml #创meta的pv&pvc## 创建网络服务kubectl apply -f opengemin…

jenkins应用2-freestyle-job

1.jenkins应用 1.jenkins构建的流程 1.使用git参数化构建&#xff0c;用标签区分版本 2.git 拉取gitlab远程仓库代码 3.maven打包项目 4.sonarqube经行代码质量检测 5.自定义制作镜像发送到远程仓库harbor 6.在远程服务器上拉取代码启动容器 这个是构建的整个过程和步骤…

StartAI:AI扩图功能,让设计更高效

在数字设计领域&#xff0c;图像的清晰度和细节至关重要。StartAI作为领先的AI设计工具&#xff0c;不断推出创新功能&#xff0c;以满足设计师们对高质量图像处理的需求。最新推出的扩图功能&#xff0c;结合了“创成式填充”技术和“PS插件”的便捷&#xff0c;为设计师们带来…

华为云投入巨资支持开发者,推动云服务与SaaS领域快速发展

近日&#xff0c;广州市迎来了一场科技界的盛会——华为云开发者日HDC.Cloud Day广州站。此次活动不仅是一场技术的盛宴&#xff0c;更是一个思维的碰撞和灵感的源泉&#xff0c;为众多开发者提供了深入学习和实践最新科技的平台。在这里&#xff0c;华为云展示了其在昇腾AI云服…

Verilog实战学习到RiscV - 3 : ICEStick 评估板点灯

收到 ICESTICK 评估板后还没好好玩。先来点个灯&#xff0c;正好把之前介绍过的工具链串起来用一下。 代码 Verilog代码只有一个顶层模块top.v&#xff0c;定义如下&#xff1a; module top(output wire D1,output wire D2,output wire D3,output wire D4,output wire D5);a…

QNX 7.0.0开发总结

1 QNX编译 1.1 基本概念 QNX可以直接使用Linux Makefile编译库和二进制&#xff0c;在Makefile文件中指定CCaarch64-unknown-nto-qnx7.0.0-g&#xff0c;或者CCx86_64-pc-nto-qnx7.0.0-g&#xff0c;保存退出后&#xff0c;运行source /qnx_sdk_path/qnxsdp-env.sh&#xff0c;…