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

目录

一,优先队列简介

二,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()const { return _cont.size(); }
	bool empty()const { return _cont.empty(); }

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

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

1,简单的对 priority_queue 进行测试

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

void test_priority_queue() {
	
	mySTL::priority_queue<int> pq;
	for (int i = 0;i < 100;++i) {
		pq.push(rand() % 60);
	}
	pq.push(100);
	while (not pq.empty()) {
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;
}

运行的结果:

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()const { return _cont.size(); }
		bool empty()const { return _cont.empty(); }

		void swap(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.mfbz.cn/a/604519.html

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

相关文章

嵌入式学习69-C++(Opencv)

知识零碎&#xff1a; QT的两种编译模式 1.debug 调试模式 …

springboot整合rabbitmq的不同工作模式详解

前提是已经安装并启动了rabbitmq&#xff0c;并且项目已经引入rabbitmq&#xff0c;完成了配置。 不同模式所需参数不同&#xff0c;生产者可以根据参数不同使用重载的convertAndSend方法。而消费者均是直接监听某个队列。 不同的交换机是实现不同工作模式的关键组件.每种交换…

泛微E9开发 选择项目类型,自动带出该类项目的预计金额(即下拉框联动浮点型数据)

1、功能背景 在用户进行项目类型选择时&#xff0c;自动带出其余的标准数据&#xff08;样例中的预计金额&#xff09;&#xff0c;如对员工进行表彰奖励时&#xff0c;不同的表彰有不同的奖励金额&#xff0c;那么我们就可以使用以下的方式来进行操作。 2、展示效果 3、实现…

WiFine通信与Wi-sun通信对比

调制速率 WiFine通信&#xff1a;(G)FSK 50Kbps~500Kbps &#xff1b;LoRa 5Kbps~37.5Kbps Wi-Sun通信&#xff1a;(G)FSK 50Kbps~300Kbps &#xff1b;QPSK/OFDM 计划中… 2、协议简介 WiFine通信&#xff1a;为低成本、低功耗、移动设备倾力打造 的轻量级、分布式无线移动…

英语新概念2-回译法-lesson13

The Greenwood Boys 绿林少年是一组流行歌手们。现在他们正在参观城市里的所有公园&#xff0c;他们明天就要到这。他们将坐火车到并且大多数小镇上的年轻人将要欢迎他们&#xff0c;明天晚上他们将要在工人俱乐部唱歌。绿林少年将在这待五天&#xff0c;在这期间&#xff0c;…

我独自升级崛起加速器推荐 我独自升级免费加速器

近期&#xff0c;《我独自升级》这部动画凭借爆棚的人气&#xff0c;在各大平台上掀起了一阵观看热潮&#xff0c;其影响力不容小觑。借此时机&#xff0c;韩国游戏巨头网石集团敏捷响应&#xff0c;顺势推出了同名游戏《我独自升级&#xff1a;ARISE》&#xff0c;为粉丝们搭建…

如何让vim支持python3

首先删除旧的vim。 sudo apt-get remove vim //输入re按下tab直接显示remove sudo apt-get remove vim-runtime sudo apt-get remove vim -tiny sudo apt-get remove vim-common 然后下载vim8源码&#xff1a; git clone https://github.com/vim/vim.git 进行编译安装…

鸿蒙开发全攻略:华为应用系统如何携手嵌入式技术开启新篇章~

鸿蒙操作系统是华为自主创新的成果&#xff0c;打破了传统操作系统的局限。通过结合嵌入式技术&#xff0c;鸿蒙实现了跨平台、跨设备的高度融合&#xff0c;提供了流畅、智能的体验。华为应用系统与嵌入式技术的结合&#xff0c;提升了性能&#xff0c;丰富了用户体验。鸿蒙与…

【stm-4】PWM驱动LED呼吸灯 PWM驱动舵机PWM驱动直流电机

1.PWM驱动LED呼吸灯 void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); //结构体初始化输出比较单元 void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef*…

RabbitMQ的五种模式

一、简单模式 简单模式&#xff08;Simple&#xff09;&#xff1a;一个生产者&#xff0c;一个消费者 package com.qiangesoft.rabbitmq.mode.simple;import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframe…

mysql集群cluster引擎在写入数据时报错 (1114, “The table ‘ads‘ is full“)

问题描述&#xff1a;mysql集群在写入数据时&#xff0c;出现上述报错 问题原因&#xff1a;表数据已满&#xff0c;一般是在集群的管理节点设置里面datamemory的值太小&#xff0c;当数据量超过该值时就会出现该问题 解决方案&#xff1a; 修改集群管理节点的config.ini里面…

JUC下的ScheduledThreadPoolExecutor详解

ScheduledThreadPoolExecutor是Java并发编程框架中一个强大且灵活的线程池实现&#xff0c;专为定时与周期性任务而设计。作为ThreadPoolExecutor的子类&#xff0c;它不仅继承了线程池管理的高效与灵活性&#xff0c;还内置了基于优先级队列的延迟任务调度机制&#xff0c;支持…

商务分析方法与工具(五):Python的趣味快捷-文件和文件夹操作自动化

Tips&#xff1a;"分享是快乐的源泉&#x1f4a7;&#xff0c;在我的博客里&#xff0c;不仅有知识的海洋&#x1f30a;&#xff0c;还有满满的正能量加持&#x1f4aa;&#xff0c;快来和我一起分享这份快乐吧&#x1f60a;&#xff01; 喜欢我的博客的话&#xff0c;记得…

pytest教程-41-钩子函数-pytest_runtest_teardown

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了pytest_runtest_call钩子函数的使用方法&#xff0c;本小节我们讲解一下pytest_runtest_teardown钩子函数的使用方法。 pytest_runtest_teardown 钩子函数在每个测试用例执行完成后被调用&…

游戏辅助 -- 三种分析角色坐标方法(CE、xdbg、龙龙遍历工具)

所用工具下载地址&#xff1a; https://pan.quark.cn/s/d54e7cdc55e6 在上次课程中&#xff0c;我们成功获取了人物对象的基址&#xff1a;[[[0xd75db8]1C]28]&#xff0c;而人物血量的地址则是基址再加上偏移量278。 接下来&#xff0c;我们需要执行以下步骤来进一步操作&a…

JSP技术讲解

目录 1、JSP简介 2、JSP体验 3、JSP运行原理 4、JSP基本语法 5、JSP指令 6、JSP内置九大对象 7、JSP标签 8、JSP配置 9、JSP排错 10、总结 在前面的Servlet学习中发现Servlet本质是一个java程序&#xff0c;因此Servlet更加擅长编写程序的业务逻辑&#xff0c;而如果要…

BACnet到OPC UA的楼宇自动化系统与生产执行系统(MES)整合

在智能制造的浪潮下&#xff0c;一家位于深圳的精密电子制造企业面临着前所未有的挑战&#xff1a;如何高效地将楼宇自动化系统与生产执行系统&#xff08;MES&#xff09;整合&#xff0c;实现能源管理与生产流程的精细化控制。这家企业的楼宇控制系统使用的是BACnet协议&…

牛客NC97 字符串出现次数的TopK问题【中等 哈希+优先级队列 Java/Go】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/fd711bdfa0e840b381d7e1b82183b3ee 核心 哈希&#xff0c;优先级队列Java代码 import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返…

思维导图网页版哪个好?2024年值得推荐的8个在线思维导图软件!

思维导图如今已成为一种常用的工具&#xff0c;帮助我们清晰地组织和整理信息。随着科技的发展&#xff0c;思维导图的产品形态也经过多轮迭代&#xff0c;从最初的本地客户端过渡到基于云的 Web 端&#xff0c;各类网页版思维导图软件应运而生&#xff0c;它们方便快捷&#x…

【3dmax笔记】035: 车削修改器

一、车削修改器介绍 车削&#xff1a;图形通过绕轴旋转来创建三维效果。 开放的样条线&#xff0c;车削之后是面片。闭合的样条线&#xff0c;车削之后&#xff0c;是实体。 一、车削修改器实例 绘制高脚杯&#xff0c;首先在前视图绘制如下二维图形。 添加一个车削的修改器…
最新文章