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.mzph.cn/diannao/8986.shtml

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

相关文章

AcWing 4993 FEB

4993. FEB - AcWing题库 大佬亲笔 将原串分成三段&#xff1a; FFF|E.....B|FFF 先合并中间段&#xff0c;再合并两边的段 #include <iostream> #include <cstring> #include <algorithm> #include <string> #include <queue&g…

STM32--LoRa通信模块

ATK-LORA-01_V3.0(V3.0 是版本号&#xff0c;型号是 ATK-LORA-01 &#xff0c;下面均以 ATK-LORA-01表示该产品) 是 ALIENTEK 推出的一款体积小、微功率、低功耗、高性能远距离 LORA 无线串口模块。模块设计是采用高效的 ISM 频段射频 SX1278 扩频芯片&#xff0c;模…

GaussianBody:基于3D高斯散射的服装人体重建

GaussianBody: Clothed Human Reconstruction via 3d Gaussian Splatting GaussianBody&#xff1a;基于3D高斯散射的服装人体重建 Mengtian Li1,2,3, Shengxiang Yao1, Zhifeng Xie1,3,2, Keyu Chen4,2, Yu-Gang Jiang2 李梦田 1,2,3 、姚胜祥 1 、谢志峰 1,3, 2 、陈科宇 4, …

TriCore: 从RTOS内核的角度看CSA

今天尝试从RTOS内核的角度来看看 TriCore 的 CSA。 CSA的细节信息可以参考前一篇文章 TriCore User Manual 笔记 1-CSDN博客 CSA 的全称是 Context Save Area&#xff0c;顾名思义就是专门用来保存上下文的一块存储区域。 既然是上下文使用&#xff0c;那必然要求低延迟&…

Linux域名解析

1.hosts:windows c盘下面 Linux: /etc/hosts 作用:实现名字解析&#xff0c;主要为本地主机名、集群节点提供快速解析。平面式结构&#xff0c;集中式数据库。 缺点:不便于查询更新 2.DNS:域名系统 作用:实现名字解析(分层性&#xff0c;层次性) FQDN:完全合格域名/全称域…

开源离线AI笔记应用

前言 Reor 是一款人工智能驱动的桌面笔记应用程序&#xff0c;它能自动链接相关笔记、回答笔记中的问题并提供语义搜索。所有内容都存储在本地&#xff0c;支持 Windows、Linux 和 MacOS。Reor 站在 Ollama、Transformers.js 和 LanceDB 等巨头的肩膀上&#xff0c;使 LLM 和嵌…

基于Springboot的校园悬赏任务平台(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的校园悬赏任务平台&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构…

ANSI转义序列

一、ASCII码 ASCII&#xff08;American Standard Code for Information Interchange&#xff0c;美国信息交换标准代码&#xff09;最初的设计是一个7位的字符编码&#xff0c;使用了从0到127的数字来表示字符。这意味着它总共可以表示128个不同的字符。这包括了英文大小写字…

如何从未入库的gerrit中撤销一个文件

用一个例子说明 比如有一个提交里面的default.xml的修改没有必要&#xff0c;需要从未入库的gerrit中移除 步骤如下&#xff1a; 1.做reset操作 git reset HEAD^ packages/SettingsProvider/res/values/defaults.xml 2.做checkout操作 git checkout packages/SettingsProv…

FileLink文件摆渡技术解析:如何实现数据的安全摆渡与隔离

文件摆渡系统&#xff0c;这一现代科技名词&#xff0c;蕴含着深刻的科技内涵和广泛的应用前景。简而言之&#xff0c;文件摆渡系统是一种高效、安全的文件传输工具&#xff0c;它能够在不同的网络环境之间实现文件的快速、稳定传输。在今天的数字化时代&#xff0c;随着数据量…

压缩机继电器EOCRDS-30NY7Q升级后型号:EOCRDS3-30S

EOCR-DS3系列型号&#xff1a; EOCRDS3-05S EOCRDS-05S EOCRDS1-05S EOCRDS3-30S EOCRDS-30S EOCRDS1-30S EOCRDS3-60S EOCRDS-60S EOCRDS1-60S EOCRDS3-05W EOCRDS-05W EOCRDS1-05W EOCRDS3-30W EOCRDS-30W EOCRDS1-30W EOCRDS3-60W EOCRDS-60W EOCRDS1-60W EOCR-DS3T-…

【递归、回溯和剪枝】二叉树中的深搜

⼆叉树中的深搜深度优先遍历&#xff08;DFS&#xff0c;全称为 Depth First Traversal&#xff09;&#xff0c;是我们树或者图这样的数据结构中常⽤的⼀种遍历算法。这个算法会尽可能深的搜索树或者图的分⽀&#xff0c;直到⼀条路径上的所有节点都被遍历完毕&#xff0c;然后…

数据结构的堆(c语言版)

一.堆的概念 1.堆的基本概念 在计算机科学中&#xff0c;堆是一种特殊的数据结构&#xff0c;通常用于实现优先队列和动态分配内存。 2.堆的特征 堆是一个完全二叉树&#xff0c;它具有以下两个主要特性&#xff1a; 堆序性&#xff1a;对于最大堆&#xff0c;在堆中的任意节…

RockChip Android13 添加/删除ListPreference方法

概述: 本章将讲述在Android添加或删除ListPreference的几种方法,并以EthernetSettingsActivity为例,添加/删除一项ListPreference: 默认效果图: 添加后效果图: 方法一: 1、全部添加xml 在Activity类中使用addPreferencesFromResource()方法解析XML文件并添加Prefere…

glog的编译和使用

文章目录 glog的编译和使用概述笔记测试工程glog0.7这个版本是有问题的工程的预处理宏日志测试代码好使的代码效果备注 - 只有C风格的日志才好使备注 - glog用不到gflagEND glog的编译和使用 概述 想在DLL中打些日志&#xff0c;测试用。 没用起来。 将gflags和gtest都测试编…

离开大厂创业一年,从未受过这么大的打击!

大家好&#xff0c;我是程序员鱼皮。时间过得真快呀&#xff0c;转眼间我从腾讯出来、自主创业竟然已经整整一年了&#xff01;上周末也带团队同学们搞了场公司周年庆团建。 我自己是一个很喜欢、也很注重复盘总结的人&#xff0c;这么重要的时间&#xff0c;当然要对过去的一…

预测市场?预测股票?如何让预测有更高的准确率?

我们发现在足球赛中&#xff0c;只要知道一个简单的讯息&#xff08;主队过去的获胜机率超过一半&#xff09;&#xff0c;预测力就会明显好过随便乱猜。如果再加上第二个简单的讯息&#xff08;胜负纪录较佳的队伍会略占优势&#xff09;&#xff0c;可以再进一步提升预测力。…

Centos固定静态ip地址

这里我用的是Vmware虚拟机搭建的三台机器 进入 cd /etc/sysconfig/network-scripts然后使用 ip addr命令&#xff0c;查看自己虚拟机的以太网地址。 我这里是ens33 上面的第一个选项是本地环回地址&#xff0c;不用管它 然后查看刚刚进入的network-scripts目录下的文件 找到…

ChatPPT开启高效办公新时代,AI赋能PPT创作

目录 一、前言二、ChatPPT的几种用法1、通过在线生成2、通过插件生成演讲者模式最终成品遇到问题改进建议 三、ChatPPT其他功能 一、前言 想想以前啊&#xff0c;为了做个PPT&#xff0c;我得去网上找各种模板&#xff0c;有时候还得在某宝上花钱买。结果一做PPT&#xff0c;经…

2.1初识Spark

Spark于2009年诞生&#xff0c;最初是加州大学伯克利分校的研究项目。2013年加入Apache孵化器项目&#xff0c;2014年成为Apache顶级项目。Spark以内存内运算技术为核心&#xff0c;包含多个计算框架&#xff0c;成为大数据计算领域的后起之秀&#xff0c;打破了Hadoop的基准排…