C++——priority_queue模拟实现

news2025/7/19 8:31:26

目录

前言

一、优先级队列介绍

二、优先级队列实现

向上调整

向下调整

 三、仿函数

 总结


前言

上一篇文章我们讲了stack和queue,这两个容器是容器适配器,本质上是一种复用,那本篇文章要讲的优先级队列也是一个容器适配器,我们一起来看看吧!


一、优先级队列介绍

通过文档可以看到,优先级队列也是一个容器适配器,它的底层是vector,但是除了这个适配器以外还有一个模版参数,这个模版参数是干什么的呢?这个模版参数是去控制优先级的,控制大的优先级高,还是小的优先级高,这里就涉及到一个仿函数的概念,那我们待会把主体逻辑实现完后再来把这个仿函数给套上,这里需要注意的是,虽然优先级队列也带队列两个字,但是它已经不符合队列的特性了,还有双端队列deque也是,不符合队列的特性,优先级队列更加像堆,也就是顺序结构的二叉树


二、优先级队列实现

那就跟栈和队列是一样的,我们先搭出来一个基本框架


namespace hx
{
	template<class T, class Container = vector<T>>>
	class priority_queue
	{
	public:
		void push(const T& val)
		{
			_con.push_back(val);
		}

		void pop()
		{
			_con.pop_back();
		}

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

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

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

	private:
		Container _con;
	};
}

向上调整

那这里的push仅仅这么写肯定是不行的,因为我们要维持堆的结构的特性,大堆就是所有的父亲都大于或等于孩子,小堆是所有的父亲都小于或等于孩子,库里实现的默认是大堆,那我们也来跟着实现一个大堆,那插入完成后,这个孩子就得去向上调整,找到自己合适的位置,下面用一张图来解释

插入的那个位置就是孩子,然后定义一个父亲,当父亲还存在,就去判断父亲是否大于孩子,大就交换然后继续向上调,不大就结束

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

向下调整

pop也是不能直接把最后一个数据给删了,这样删除毫无意义,那删除的具体做法应该是什么呢,删除应该是删堆顶元素,但是又不能直接删,直接删堆就乱了,应该让最后一个元素与堆顶元素交换,再删除,然后再从堆顶开始去向下调整

 向下调整多了一个选孩子的过程,因为我们是大堆,那也就是要把大的那个孩子选出来,再进行交换,没有孩子了或者父亲大于孩子了就结束

void AdjustDown(int parent)
{
	int child = parent * 2 + 1;
	while (child < _con.size())
	{
		if (child + 1 < _con.size() && (_con[child] < _con[child + 1]))
		{
			++child;
		}

		if (_con[parent] < _con[child])
		{
			swap(_con[child], _con[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

我们默认是左孩子大,在循环条件这里,在向下调整之前我们就已经调用了pop_back,所以就这里的size求出来是没问题的,然后还有一个注意的点,我们要拿左孩子和右孩子去比,要先去判断右孩子在不在,右孩子存在且大于左孩子,再让child变成右孩子


 三、仿函数

那现在我们把大堆给实现好了,那如果要实现小堆也要再写一个吗?像list的迭代器那里,普通迭代器和const迭代器只有返回值不一样,写两份就太冗余了,在这里也是一样,小堆就是换个符号,其他的就要全写一份吗,肯定是不会的,那就要引入一个仿函数的概念,仿函数就是C语言中的函数指针,函数指针这个东西定义起来太复杂了,C++就引入了仿函数,那什么叫仿函数呢,就是重载了operator()的类,那我们来自己写一个仿函数试试

namespace hx
{
    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;
	    }
    };
}

int main()
{
	hx::Less<int> ls;
	cout << ls(1, 2) << endl;

	hx::Greater<int> gt;
	cout << gt(1, 2) << endl;

	return 0;
}

ls对象先定义出来,ls(1, 2)就会被转化成ls.operator()(1, 2),就调到了仿函数如果我们有自己比较大小的方式,我们就可以把我们的仿函数传进去给优先级队列用,也就是说仿函数是可以做到在外部去控制内部的,库里面的仿函数就是这两个,less和greater,是小写的,我们这里用了大写,跟库区分开,那我们把自己的代码套上仿函数


namespace hx
{
	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 = Less<T>>
	class priority_queue
	{
	private:
		void AdjustUp(int child)
		{
			Compare _cmp;

			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (_cmp(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

		void AdjustDown(int parent)
		{
			Compare _cmp;

			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() && _cmp(_con[child], _con[child + 1]))
				{
					++child;
				}

				if (_cmp(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

	public:
        priority_queue()
        {}

		void push(const T& val)
		{
			_con.push_back(val);
			//向上调整
			AdjustUp(_con.size() - 1);
		}

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

			//向下调整
			AdjustDown(0);
		}

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

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

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

	private:
		Container _con;
	};
}

这里的Less就用我们自己写的,优先级队列是默认大堆,用Less,那小堆就是Greater,用Compare实例化出_cmp对象,在父亲和孩子比较以及向下调整选左右孩子的时候都用_cmp对象去调用了operator()

要注意的是Less比较的是小于<,这里和传参的顺序是有关系的,我们要把大的放在右边,_cmp(_con[parent], _con[child]),孩子大于父亲就交换,要满足<的顺序,就得把孩子放在右边,如果是小堆,那就用Greater,就是要父亲大于孩子才去交换,_cmp(_con[parent], _con[child]),greater是>,刚好父亲在左边,所以我们这么写是能满足需求的,就看外面传什么就可以


那这样是不是就能解决所有场景的问题呢?显然不是,我们把之前的日期类拿出来,在优先级队列里存储日期的指针

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);
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	hx::priority_queue<Date*> pq;
	pq.push(new Date(2025, 1, 9));
	pq.push(new Date(2025, 2, 9));
	pq.push(new Date(2025, 3, 9));
	while (!pq.empty())
	{
		cout << *pq.top() << " ";
		pq.pop();
	}
	cout << endl;

	return 0;
}

我们多次运行发现,好像每一次的结果都不一样啊,这里默认大堆应该是3月2月1月的,那为什么出现不一样的结果呢?首先我们要知道,在向上调整去比较孩子和父亲的大小时会用到仿函数,仿函数内部就会比较出两个日期的大小,而自定义类型不能用运算符,所以日期类需要重载运算符才可以,重载了之后,就可以去调用仿函数了,库里面的仿函数其实就是我们前面那样写的,那因为我们插入的是指针,那仿函数接收到的参数就是两个日期类的指针,所以我们比较的是指针的大小!!!才会导致每次的结果都不一样,那现在就需要我们自己来控制了,自己写一个仿函数传进去

struct LessDate
{
	bool operator()(const Date* px, const Date* py)
	{
		return *px < *py;
	}
};

参数是指针,我们比较的是解引用之后的日期大小

	hx::priority_queue<Date*, vector<Date*>, LessDate> pq;
	pq.push(new Date(2025, 1, 9));
	pq.push(new Date(2025, 2, 9));
	pq.push(new Date(2025, 3, 9));
	while (!pq.empty())
	{
		cout << *pq.top() << " ";
		pq.pop();
	}
	cout << endl;

	return 0;
}

现在运行多次,结果也都是一样的!!!

 具体的每一步的调用逻辑是这样的,已经用箭头和步骤表明了,大家可以结合着图来理解


 如果不传仿函数,用我们自己写的Less,那大家可以看到,跟上一张图比,是没有步骤四的,在比较x < y这步,按F11是进不去的,就是因为这里比较的两个指针的大小,不是函数调用就进不去


 总结

本篇文章我们优先级队列,引出了仿函数,他替代的是C语言的函数指针,实现了更加灵活的调用方式,可以做到在外部控制内部的细节,在最后的日期类的那里因为涉及到多个类了,所以调用可能会比较复杂,大家没太看明白可以多看两遍,尤其是看看图,或者是去调试一下,按F11进去,看看会走到哪里,总之,仿函数也是非常重要的一部分,也会经常用,大家肯定是会越来越熟练的,如果大家觉得小编写的不错,可以给一个一键三连,感谢大家的支持!!!

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

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

相关文章

计算机网络基础:DOS命令、批处理脚本常见命令

目录 1. DOS 基础命令 1. echo 、 > 编写文件 2. type 读取文件 3. copy con 整段编写 4. attrib 命令 5. 快速生成空文件 6. 修改关联性 7. 关机shutdown 8. 复制文件copy、移动文件move 9. 重命名ren 2. 批处理 2.1 简单显示 2.2 死循环 2.3 定时关机小程序 …

ArcGIS Pro热力图制作指南:从基础到进阶

引言 在地理信息科学领域&#xff0c;热力图作为一种直观的数据可视化手段&#xff0c;广泛应用于展示空间数据的密度和热度分布。ArcGIS Pro&#xff0c;作为一款强大的地理信息系统&#xff08;GIS&#xff09;软件&#xff0c;为我们提供了制作热力图的便捷工具。本文将从基…

智慧校园系统在学生学习与生活中的应用

随着科技的快速发展&#xff0c;智慧校园系统逐渐成为现代教育不可或缺的一部分。它整合了先进的信息技术、物联网技术以及人工智能等&#xff0c;旨在构建一个全面、智能、个性化的学习与生活环境。对于学生而言&#xff0c;这一系统不仅能够极大地提高学习效率&#xff0c;还…

第三十四周学习周报

目录 摘要Abstract1 文献阅读1.1 相关知识1.1.1 贝叶斯优化1.1.2 注意力机制复习 1.2 模型框架1.3 实验分析 总结 摘要 在本周阅读的文献中&#xff0c;作者提出了一种将注意力机制与LSTM相结合的模型AT-LSTM。虽然传统LSTM通过其门控机制能有效捕捉时间序列中的长期依赖关系&…

第4章 信息系统架构(三)

4.3 应用架构 应用架构的主要内容是规划出目标应用分层分域架构&#xff0c;根据业务架构规划目标应用域、应用组和目标应用组件&#xff0c;形成目标应用架构逻辑视图和系统视图。从功能视角出发&#xff0c;阐述应用组件各自及应用架构整体上&#xff0c;如何实现组织的高阶…

一周学会Flask3 Python Web开发-flask3模块化blueprint配置

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 我们在项目开发的时候&#xff0c;多多少少会划分几个或者几十个业务模块&#xff0c;如果把这些模块的视图方法都写在app.py…

Android开发-深入解析Android中的AIDL及其应用场景

深入解析 Android 中的 AIDL 及其应用场景 1. 前言2. AIDL 的核心概念3. AIDL 的实现步骤3.1. 定义 AIDL 接口文件3.2. 实现服务端&#xff08;Service&#xff09;3.3. 客户端绑定与调用 4. AIDL 的典型应用场景4.1. 多进程应用4.2. 与系统服务交互4.3. 高性能 IPC4.4. 跨应用…

基于python的旅客游记和轨迹分析可视化系统设计(新)

项目地址&#xff1a;基于python旅客游记和轨迹分析可视化系统设计&#xff08;新&#xff09; 摘 要 旅客游记和轨迹分析可视化系统是一种能自动从网络上收集信息的工具&#xff0c;可根据用户的需求定向采集特定数据信息的工具&#xff0c;本项目通过研究爬取微博网来实现旅…

HackTheBox靶场之Unrested 漏洞CVE-2024-42327 《CUser 类中的 user.get 函数中的 SQL 注入》

目录 信息收集 web指纹收集 wappazer Nmap指纹收集 Nmap分析总结 漏洞利用 漏洞CVE-POC执行流程 信息收集 web指纹收集 wappazer 看着有apache2.4.52 那么可以试着找一下 apache的历史cve看可以利用否 使用用户名密码&#xff1a;matthew / 96qzn0h2e1k3 登录成后后…

uniprot系列相关数据库介绍

https://www.uniprot.org/uniprotkb/P49711/entry#family_and_domains 上面是一个CTCF human蛋白质条目&#xff0c; 我们来看看family & domain条目中涉及到的蛋白质家族以及结构域数据库&#xff1a; 1&#xff0c;funfam&#xff1a; CATH: Protein Structure Classi…

基于AIGC的图表自动化生成工具「图表狐」深度评测:如何用自然语言30秒搞定专业级数据可视化?

一、工具核心定位&#xff1a;自然语言驱动的数据可视化 作为数据科学从业者&#xff0c;我们常面临非技术同事的图表制作需求。传统流程需经历数据清洗→结构转换→图表配置→样式调整四大阶段&#xff0c;耗时且易出错。 图表狐&#xff08;官网预览&#x1f447;&#xff…

rpc到自己java实现rpc调用再到rpc框架设计

目录 rpc(Remote Procedure Call)rpc一般架构为什么要引入rpc自己实现rpc调用1. 新建一个maven项目&#xff0c;加入hessian依赖2. 服务端3. Stub代理4. 客户端测试输出5. rpc程序分析附 请求参数和序列化程序 6. 总结 回顾RPCRPC 序列化协议RPC 网络协议注册中心的引入dubbo框…

Milvus向量数据库可视化客户端Attu

概述 关于Milvus的介绍&#xff0c;可搜索网络资料。Milvus的使用还在摸索中&#xff1b;打算写一篇&#xff0c;时间待定。 关于Attu的资料&#xff1a; 官网GitHub文档 对于Milvus的数据可视化&#xff0c;有如下两个备选项&#xff1a; Milvus_cli&#xff1a;命令行工…

【落羽的落羽 数据结构篇】顺序结构的二叉树——堆

文章目录 一、堆1. 概念与分类2. 结构与性质3. 入堆4. 出堆 二、堆排序三、堆排序的应用——TOP-K问题 一、堆 1. 概念与分类 上一期我们提到&#xff0c;二叉树的实现既可以用顺序结构&#xff0c;也可以用链式结构。本篇我们来学习顺序结构的二叉树&#xff0c;起个新名字—…

基于STM32的智能农业大棚环境控制系统

1. 引言 传统农业大棚环境调控依赖人工经验&#xff0c;存在控制精度低、能耗高等问题。本文设计了一款基于STM32的智能农业大棚环境控制系统&#xff0c;通过多参数环境监测、作物生长模型与精准执行控制&#xff0c;实现大棚环境的智能优化&#xff0c;提高作物产量与品质。…

Git常见命令--助力开发

git常见命令&#xff1a; 创建初始化仓库&#xff1a; git 将文件提交到暂存区 git add 文件名 将文件提交到工作区 git commit -m "注释&#xff08;例如这是发行的版本1&#xff09;" 文件名 查看状态 如果暂存区没有文件被提交显示&#xff1a; $ git status On…

一:将windows上的Python项目部署到Linux上,并使用公网IP访问

windows中python的版本&#xff1a;python3.13.1&#xff0c;项目使用的是虚拟环境解释器 linux系统&#xff1a;仅有python3.6.7 服务器&#xff1a;阿里云服务器有公网IP&#xff0c;访问端口XXXX 在linux上安装python3.13.1 linux中如果是超级管理员root&#xff0c;执行所…

【数据标准】数据标准化是数据治理的基础

导读&#xff1a;数据标准化是数据治理的基石&#xff0c;它通过统一数据格式、编码、命名与语义等&#xff0c;全方位提升数据质量&#xff0c;确保准确性、完整性与一致性&#xff0c;从源头上杜绝错误与冲突。这不仅打破部门及系统间的数据壁垒&#xff0c;极大促进数据共享…

计算机视觉:经典数据格式(VOC、YOLO、COCO)解析与转换(附代码)

第一章&#xff1a;计算机视觉中图像的基础认知 第二章&#xff1a;计算机视觉&#xff1a;卷积神经网络(CNN)基本概念(一) 第三章&#xff1a;计算机视觉&#xff1a;卷积神经网络(CNN)基本概念(二) 第四章&#xff1a;搭建一个经典的LeNet5神经网络(附代码) 第五章&#xff1…

七星棋牌顶级运营产品全开源修复版源码教程:6端支持,200+子游戏玩法,完整搭建指南(含代码解析)

棋牌游戏一直是移动端游戏市场中极具竞争力和受欢迎的品类&#xff0c;而七星棋牌源码修复版无疑是当前行业内不可多得的高质量棋牌项目之一。该项目支持 6大省区版本&#xff08;湖南、湖北、山西、江苏、贵州&#xff09;&#xff0c;拥有 200多种子游戏玩法&#xff0c;同时…