C++ 模拟实现 priority_queue(优先队列)

news2024/11/17 13:57:08

目录

一,优先队列简介

二,priority_queue 的内部实现原理

三,模拟实现 priority_queue

1,模板参数与数据结构

2,构造

3,辅助功能(堆的有序化,建立堆)

4,核心功能

四,简单的测试 priority_queue 与完整代码


一,优先队列简介

1,什么是优先队列:

优先队列是一种特殊的队列数据结构,它具有队列先进先出(FIFO)的特性,但是在取出元素时会优先取出具有最高优先级的元素。这意味着插入的元素会按照一定的优先级规则进行排序,而不是简单地按照插入顺序。

2,STL中的优先队列:

在 C++ 的 STL 中,priority_queue 是一个容器适配器,用于实现优先队列的功能。priority_queue 提供了插入元素、移除最高优先级元素、访问堆顶元素等常用操作,操作十分便捷。

3,优先队列的使用场景:

① 任务调度:在操作系统中,任务调度往往需要根据各个任务的优先级来决定执行顺序,优先队列可以很好地满足这种场景。
② 事件处理:在网络编程或并发编程中,事件处理的顺序可能会影响程序性能,优先队列可以帮助我们按照一定的规则处理事件顺序。
③ 一些图论算法:很多图论算法(例如 Dijkstra 算法)中都需要根据权值来确定优先级,这时候可以使用优先队列来保存待处理的顶点,并按照权值进行排序。

优先队列示意图:

二,priority_queue 的内部实现原理

1,堆结构

优先队列通常是基于二叉堆来实现的,二叉堆能够很好的实现优先队列的基本操作(为方便表述,接下来将二叉堆简称为堆)。那么,什么是堆呢?

堆(Heap)是一种特殊的树形数据结构,根据不同的排列方式通常可以分为最大堆和最小堆两种类型。以最大堆为例:它的特点是父节点的键值总是大于或等于任何一个子节点的键值。这种性质决定了堆的根节点(也就是堆顶)的键值是最大的。

堆通常是一个完全二叉树,除了最底层,其它层全部都会被填充满,最底层从左到右填充。我们用数组就可以很方便地表示一个堆,而且堆的操作也多是通过数组的索引进行的。

二叉堆示意图:

2,二叉堆的表示

如果我们用指针来表示堆结构,那么每个元素都需要左子节点,右子节点,父节点三个指针;而如果我们使用数组来表示,就会变得特别方便,不需要指针就可以沿着树上下的移动。具体方式如下:arr[0] 可以用来当作根节点,对于 arr[k],它的左右两个子节点的索引分别为 2k + 1 ,2k + 2,它的父节点的索引则为 (k - 1) / 2。

(有些表示方法用 arr[1] 来作为根节点,而 arr[0] 则用来作为哨兵闲置)

用数组表示堆的示意图:

三,模拟实现 priority_queue

priority_queue 在 STL 中以底部的容器来完成几乎全部的工作,所以它并没有被归类为容器(container),而是被归类为容器适配器(container adapter)。

priority_queue 只有队列中的顶部元素(优先级最高的元素)才能被外界取用,所以 priority_queue 不提供遍历功能,这也代表着它并不提供迭代器

1,模板参数与数据结构

template<class T, class Compare = std::less<T>, class Container = std::vector<T>>
class priority_queue {
private:
	Container _cont;	// 底层容器
	Compare _cmp;		// 比较方式
	// ...
}

前面有说到我们会用数组来作为底层容器来表示堆,为了方便底层容器的扩容,这里选择缺省使用 vector 来作为底层容器。

2,构造

public:
	// 构造
	priority_queue() {}

	template<class input_iterator>
	priority_queue(input_iterator begin, input_iterator end) : _cont(begin, end) {
		make_heap();
	}

	priority_queue(const std::initializer_list<T>& lt) :_cont(lt) {
		make_heap();
	}

make_heap() 函数会将 _cont 中的元素给调整成一个堆,具体实现稍后就会给出。

3,辅助功能(堆的有序化,建立堆)

首先是寻找父节点与子节点的索引功能:

private:
	size_t parent_idx(size_t idx) { return (idx - 1) / 2; }
	size_t left_child_idx(size_t idx) { return 2 * idx + 1; }
	size_t right_child_idx(size_t idx) { return 2 * idx + 2; }

我们对优先队列进行一些操作时会破环堆的结构,之后我们会遍历堆,将堆的结构给恢复回来。这个恢复的过程就叫做堆的有序化

堆的有序化分为两种情况:

① 当某个节点的优先级上升,或者是我们在堆底(底层容器的尾部)加入了一个新的元素时,我们需要自下而上的对堆进行调整。

② 当某个节点的优先级下降时,我们需要自上而下的对堆进行调整。

先来看看自下而上的调整:

private:
	void adjust_up_heap(size_t child) {
		size_t parent = parent_idx(child);
		while (child != 0 and _cmp(_cont[parent], _cont[child])) {				
			std::swap(_cont[parent], _cont[child]);
			child = parent;
			parent = parent_idx(child);							
		}
	}

如果堆的结构因为某个节点比它的父节点优先级更高而被打破,那么我们就需要交换当前的节点与父节点。不过交换之后可能还会在父节点处继续打破堆的结构,所以我们要将父节点更新为当前节点,继续进行交换,直到堆恢复完成。

再来看看自上而下的调整:

private:
	void adjust_down_heap(size_t parent) {
		size_t n = _cont.size();
		size_t child = left_child_idx(parent);
		while (child < n) {
			if (child + 1 < n and _cmp(_cont[child], _cont[child + 1])) {
				++child;	// 将 left_child 变为 right_child
			}
			if (_cmp(_cont[parent], _cont[child])) {
				std::swap(_cont[parent], _cont[child]);
				parent = child;
				child = left_child_idx(parent);
			}
			else break;
		}
	}

如果堆的结构因为某个节点比它的子节点优先级更小而被打破,那么我们就需要将它和它的两个子节点中优先级更高的那一位进行交换。同样的,交换之后子节点处可能还会破环堆的结构,所以我们需要将子节点更新为当前节点,继续进行交换,直到堆恢复完成。

引用《算法 (第四版)》 中的一段话:

如果我们把堆想象成一个严密的黑社会组织,每个子结点都表示一个下属(父结点则表示它的直接上级),那么这些操作就可以得到很有趣的解释。adjust_up_heap() 表示一个很有能力的新人加入组织并被逐级提升(将能力不够的上级踩在脚下),直到他遇到了一个更强的领导。adjust_donw_heap() 则类似于整个社团的领导退休并被外来者取代之后,如果他的下属比他更厉害,他们的角色就会交换,这种交换会持续下去直到他的能力比其下属都强为止。

最后我们来看看怎么将一个无序的容器调整成一个堆:

private:
	void make_heap() {
		size_t tail_child = _cont.size() - 1;
		size_t parent = parent_idx(tail_child);
		size_t stop = -1;
		while (parent != stop) {
			adjust_down_heap(parent--);
		}
	}

我们从最尾部的元素的父节点开始向上遍历,每次都将当前的节点进行向下调整操作。

4,核心功能

priority_queue 的核心功能如下:插入元素(push),删除顶部元素(pop),获取顶部元素(top),检查优先队列是否为空(empty),获取元素数量(size)。

public:
	// 核心功能
	void push(const T& data) {
		_cont.push_back(data);
		adjust_up_heap(_cont.size() - 1);
	}

	void pop() {
		_cont.front() = _cont.back();
		_cont.pop_back();
		adjust_down_heap(0);
	}

	T& top() { return _cont.front(); }
	size_t size() { return _cont.size(); }
	bool empty() { return _cont.empty(); }

	void swap(const priority_queue& pq) {
		std::swap(_cont, pq._cont);
		std::swap(_cmp, pq._cmp);
	}

四,简单的测试 priority_queue 与完整代码

1,简单的对 priority_queue 进行测试

我们可以大量的对 priority_queue 进行插入,然后再一个一个的取出顶部的元素。

void test_priority_queue() {
	std::vector<int> vec;
	for (int i = 0;i < 100;++i) {
		vec.push_back(rand() % 60);
	}
	mySTL::priority_queue<int>heap(vec.begin(), vec.end());
	heap.push(100);
	while (not heap.empty()) {
		cout << heap.top() << " ";
		heap.pop();
	}
}

运行的结果:

2,完整代码

namespace mySTL {

	template<class T, class Compare = std::less<T>, class Container = std::vector<T>>
	class priority_queue {
	private:
		Container _cont;	// 底层容器
		Compare _cmp;		// 比较方式

	public:
		// 构造
		priority_queue() {}

		template<class input_iterator>
		priority_queue(input_iterator begin, input_iterator end) : _cont(begin, end) {
			make_heap();
		}

		priority_queue(const std::initializer_list<T>& lt) :_cont(lt) {
			make_heap();
		}


	public:
		// 核心功能
		void push(const T& data) {
			_cont.push_back(data);
			adjust_up_heap(_cont.size() - 1);
		}

		void pop() {
			_cont.front() = _cont.back();
			_cont.pop_back();
			adjust_down_heap(0);
		}

		T& top() { return _cont.front(); }
		size_t size() { return _cont.size(); }
		bool empty() { return _cont.empty(); }

		void swap(const priority_queue& pq) {
			std::swap(_cont, pq._cont);
			std::swap(_cmp, pq._cmp);
		}
		

	private:
		size_t parent_idx(size_t idx) { return (idx - 1) / 2; }
		size_t left_child_idx(size_t idx) { return 2 * idx + 1; }
		size_t right_child_idx(size_t idx) { return 2 * idx + 2; }

	private:
		void make_heap() {
			size_t tail_child = _cont.size() - 1;
			size_t parent = parent_idx(tail_child);
			size_t stop = -1;
			while (parent != stop) {
				adjust_down_heap(parent--);
			}
		}
	
		void adjust_down_heap(size_t parent) {
			size_t n = _cont.size();
			size_t child = left_child_idx(parent);
			while (child < n) {
				if (child + 1 < n and _cmp(_cont[child], _cont[child + 1])) {
					++child;	// 将 left_child 变为 right_child
				}
				if (_cmp(_cont[parent], _cont[child])) {
					std::swap(_cont[parent], _cont[child]);
					parent = child;
					child = left_child_idx(parent);
				}
				else break;
			}
		}

		void adjust_up_heap(size_t child) {
			size_t parent = parent_idx(child);
			while (child != 0 and _cmp(_cont[parent], _cont[child])) {				
				std::swap(_cont[parent], _cont[child]);
				child = parent;
				parent = parent_idx(child);							
			}
		}


	//public:
	//	// 方便调试
	//	void print() {
	//		for (const auto& data : _cont) {
	//			std::cout << data << " ";
	//		}
	//		std::cout << std::endl;
	//	}

	};

}

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

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

相关文章

【数据结构】链表专题2

前言 本篇博客继续探讨有关链表的专题&#xff0c;这片博客的题&#xff0c;提前打个预防针&#xff0c;有点意思哦&#xff0c;哈哈哈&#xff0c;话不多说&#xff0c;进入正文 &#x1f493; 个人主页&#xff1a;小张同学zkf ⏩ 文章专栏&#xff1a;数据结构 若有问题 评论…

STM32定时器门控模式+单脉冲模式配置

1、实现功能及使用场景&#xff1a; 利用一个主定时器多个从定时器&#xff0c;主定时器控制从定时器的脉冲发送时机和发送个数。 适合用在多轴同步控制的自动化或机器人设备中&#xff0c;同时可以防止系统程序跑飞时运动轴没有停止&#xff0c;提高系统安全。 2、门控模式…

【华为】路由综合实验(基础)

【华为】路由综合实验 实验需求拓扑配置AR1AR2AR3AR4AR5PC1PC2 查看通信OSPF邻居OSPF路由表 BGPBGP邻居BGP 路由表 配置文档 实验需求 ① 自行规划IP地址 ② 在区域1里面 启用OSPF ③ 在区域1和区域2 启用BGP&#xff0c;使AR4和AR3成为eBGP&#xff0c;AR4和AR5成为iBGP对等体…

服务器数据恢复—服务器重装系统导致XFS分区丢失的数据恢复案例

服务器数据恢复环境&#xff1a; 一台服务器MD1200磁盘柜&#xff0c;通过raid卡将15块磁盘组建成一组raid5磁盘阵列。raid5阵列分配了2个lun&#xff0c;操作系统层面对lun进行分区&#xff1a;1个分区采用LVM扩容方式加入到了root_lv中&#xff0c;其余分区格式化为XFS文件系…

Java发送请求-http+https的

第一步&#xff1a;建议ssl连接对象&#xff0c;信任所有证书 第二步&#xff1a;代码同时支持httphttps 引入源码类 是一个注册器 引入这个类&#xff0c;和它的方法create 注册器&#xff0c;所以对http和https都进行注册&#xff0c;参数为id和item&#xff0c;其中http的…

在UI界面中播放视频_unity基础开发教程

在UI界面中播放视频_unity基础开发教程 前言操作步骤结语 前言 之前我写过一篇在场景中播放视频的文章&#xff0c;但是在开发中有时候也会在UI的界面中播放视频&#xff0c;这期我们做一下在UI的界面中播放视频。 操作步骤 首先在场景中创建一个Raw Image&#xff0c;UI->…

Visual 下载 NuGet包速度变慢

Visual 下载 NuGet包速度变慢 最近遇到一个问题&#xff0c;即我在使用 Visual Studio 下载 Nuget 包的时候会发现变得特别慢&#xff0c;那么该如何解决该问题呢 Visual Studio → 工具 → NuGet 包管理项 → 程序包管理设置 → 程序包源 从上面我们可以看到我使用的包源地址…

Codeforces Round 942 (Div. 2) ----- A ----- F --- 题解

前情提要&#xff1a;因为数学水平原因&#xff0c;没法给出e的证明&#xff0c;因为我也是举例归类得出的结论&#xff0c;但是按理来说应该可以利用生成数函数证明 f题也是因为数学原因加上水平有限&#xff0c;我的理解可能有偏差。 目录 A. Contest Proposal&#xff1a…

【无线通信开发应用】nRF905数据手册深度解读

希望通过两个stm32、两个nRF905无线通信模块、串口来实现两机通信。具体功能为&#xff1a; 板子A、B分别包含一个stm32单片机和一个nRF905无线模块&#xff0c;欲实现板子A、B之间的通信。 其中&#xff0c;PC端串口助手可向板子A的stm32发送字符‘A’控制板子B上的LED亮灯&am…

算法系列--多源BFS问题

&#x1f495;"对相爱的人来说&#xff0c;对方的心意&#xff0c;才是最好的房子。"&#x1f495; 作者&#xff1a;Lvzi 文章主要内容&#xff1a;算法系列–多源BFS问题 大家好,今天为大家带来的是算法系列--多源BFS问题 前言: 之前我们已经学习过单源的最短路问…

质谱原理与仪器3-笔记

质谱原理与仪器3-笔记 一、质量分析器类型1、聚焦磁场分析器&#xff1a;A、单聚焦磁场分析器B、双聚焦磁场分析器 2、四极杆质量分析器3、飞行时间质谱仪(Time of Flight MS, TOF-MS)4、离子阱质量分析器 二、质谱仪的主要性能指标1、质量范围(mass range)2、分辨率(resolutio…

面试经典150题——Z 字形变换

面试经典150题 day22 题目来源我的题解方法一 使用StringBuilder数组模拟矩阵方法二 找规律直接构造 题目来源 力扣每日一题&#xff1b;题序&#xff1a;6 我的题解 方法一 使用StringBuilder数组模拟矩阵 如果numRows是1&#xff0c;则直接返回s。 否则&#xff0c;创建nu…

python实现的基于单向循环链表插入排序

相比于定义一个循环双向链表来实现插入排序来说&#xff0c;下面的实现采用一个单向循环链表来实现&#xff0c;并且不需要定义一个单向循环链表类&#xff0c;而是把一个list&#xff08;数组/顺序表&#xff09;当成单向循环链表来用&#xff0c;list的元素是一个包含两个元素…

26.统一网关Gateway

网关的功能 1.身份认证&#xff0c;权限的校验。 2.服务的路由&#xff0c;负载均衡。用户请求被分配到哪一个微服务。一个微服务可以有多个实例&#xff0c;所以使用负载均衡。 3.请求限流。 springcloud网关实现有两种&#xff1a;gateway, zuul zuul是基于servlet实现的…

Enhancing Diffusion——利用三维透视几何约束增强扩散模型

概述 透视在艺术中被广泛研究&#xff0c;但现代高质量图像生成方法却缺乏透视精度。新的生成模型引入了几何约束&#xff0c;通过训练过程提高透视精度。这样可以生成更逼真的图像&#xff0c;并提高相关深度估计模型的性能。 最近的图像生成技术使研究人员能够创造性地进行…

TCP/IP和HTTP协议

TCP/IP OSI 七层模型在提出时的出发点是基于标准化的考虑&#xff0c;而没有考虑到具体的市场需求&#xff0c;使得该模型结构复杂&#xff0c;部分功能冗余&#xff0c;因而完全实现 OSI 参考模型的系统不多。而 TCP/IP 参考模型直接面向市场需求&#xff0c;实现起来也比较…

App一键直达,Xinstall助力提升用户体验

在这个移动互联网时代&#xff0c;App已经成为了我们日常生活中不可或缺的一部分。然而&#xff0c;每当我们在浏览器或社交平台上看到一个有趣的App推荐&#xff0c;点击下载后却往往要经历一系列繁琐的跳转和确认过程&#xff0c;这无疑大大降低了用户体验。那么&#xff0c;…

工业三废数据集(工业烟粉尘排放量、工业二氧化硫排放量、工业废水排放量)2006-2021年

01、数据介绍 工业三废是指工业生产过程中排出的废气、废水和废渣 工业二氧化硫排放量指企业在燃料燃烧和生产工艺过程中排入大气的二氧化硫数量。 工业烟粉尘排放量是指企业在生产工艺过程中排放的烟尘和粉尘等颗粒物重量。 工业废水排放量是指企业在生产过程中产生的废水…

GPG的使用

这里写自定义目录标题 安装加密程序生成加密密钥怎么备份自己的密钥就可以使用公钥加密邮件信息了 安装加密程序 下载gpg4win&#xff1a; https://www.gpg4win.org/index.html 免费的&#xff0c;如果使用的是苹果电脑&#xff0c;使用https://gpgtools.org/。 如果是linux&a…

Go Web 开发基础【用户登录、注册、验证】

前言 这篇文章主要是学习怎么用 Go 语言&#xff08;Gin&#xff09;开发Web程序&#xff0c;前端太弱了&#xff0c;得好好补补课&#xff0c;完了再来更新。 1、环境准备 新建项目&#xff0c;生成 go.mod 文件&#xff1a; 出现报错&#xff1a;go: modules disabled by G…