【C++笔记】优先级队列priority_queue的模拟实现

news2025/1/16 17:47:14

【C++笔记】优先级队列priority_queue的模拟实现

  • 一、优先级队列的介绍与使用方式
    • 1.1、优先级队列介绍
    • 1.2、优先级队列的常见使用
  • 二、优先级队列的模拟实现
    • 1.0、仿函数的介绍
    • 1.1、构造函数
    • 1.2、优先级队列的插入push
    • 1.3、优先级队列的删除(删除堆顶元素)
    • 1.4、获取堆顶元素
    • 1.5、判断是否为空
    • 1.6、优先级队列的大小size

一、优先级队列的介绍与使用方式

1.1、优先级队列介绍

优先级队列可能听名字就能想到它的功能,就是按优先级排的队列。可他到底是个什么呢?它的底层有时由什么实现的?
我们可以先翻翻文档看看:
在这里插入图片描述
从文档中我们也可以看出它其实也是一个类模板。
其中的Container这个模板参数是一个容器适配器,默认使用vector作为其底层存储数据的容器。
其实优先级队列在底层就是我们以前学过的堆,它在vector上使用了堆heap的算法将vector中元素构造堆的结构,默认情况下是大堆。

而其中的Compare这个模板参数则是能将堆灵活的转换成大堆或小堆的一个非常好的工具,在后面会对仿函数进行详细的解析。

我们再来看看它对应的接口:
在这里插入图片描述
从给出的接口我们也可以发现它和堆其实是非常相似的。

1.2、优先级队列的常见使用

上面说过优先级队列默认是大堆,那我们就来验证一下:
在这里插入图片描述
如果想要给成小堆,则要传入一个仿函数:
在这里插入图片描述
因为是缺省参数,所以一定要从左到右传完。这里的greater< int > 其实就是比较方法。

二、优先级队列的模拟实现

1.0、仿函数的介绍

仿函数,也称函数对象,是STL六大组件中的一个,他可以想其他类一样定义对象,也可以像正常函数一样调用。它其实有点像我们在C语言中学过的函数指针。

但是我们在这里并不能一次性讲解完它的所有内容,所以今天就仅基于priority_queue的实现来对它进程粗略的介绍。

实际上,就实现意义而言,函数对象这个名字更加贴切:一种具有函数特质的对象。但是,仿函数似乎能更加符合的描述他的行为。所以这里我们就采用仿函数这种叫法。

在学习STL之前我们就已经了解了泛型编程的概念,C++引入了模板让我们的编程能够随意的控制数据类型,现在引入了仿函数的概念,让我们能够控制逻辑。

我们上面用到的greater< int>就是一个仿函数,我们也可以对应的查查文档看看:
在这里插入图片描述

我们再来看看它的简单应用吧:

模拟实现仿函数:

template<class T>
class Less
{
public:
    bool operator()(const T& x, const T& y)//less和greater需要实现的就是一个比较,所以这里的返回值是bool类型
    {
        return x < y;
    }
};
template<class T>
class Greater
{
public:
    bool operator()(const T& x, const T& y)
    {
        return x > y;
    }
};

其实仿函数主要是通过在类中实现一个特殊的运算符重载——operator()来达成的。
然后我们就可以将上面的测试换成我们自己写的仿函数了:
在这里插入图片描述
其达到的效果也是一样的。
其实仿函数之所以能像函数一样直接调用(直接对象名+括号),其实是编译器在底层做了处理,编译器在底层其实是自动调用了类中的operator(),而我们其实也可以显示的写出来:
在这里插入图片描述
从这点我们可以看出,仿函数其实也没有多神奇,一切都是编译器的功劳。

1.1、构造函数

模板参数的设计:
首先,对于函数模板的设计,我们和库里面对其,给了三个参数,分别表示参数存入容器的参数类型,容器类型和仿函数,其中默认的仿函数是less,建大堆:

template<class T, class Container = std::vector<T>, class Compare = less<T>>
class Priority_queue
{
public:
    //...
private:
    Container _con;
    Compare _cmp;

}; 

无参构造函数:
无参构造函数我们其实可以不写的,因为我们使用容器适配器的好处就是我们可以直接使用其他容器的无参构造函数,但是因为后面的一些原因(调用可能存在歧义),我们还得将它写上。
其实我们只需将它写出来即可,我们什么都不用做:

	// 无参构造函数
	Priority_queue() {

	}

迭代器区间构造:
因为我们这里使用的是vector来适配,vector中也有迭代器区间构造,所以我们也很轻松,直接调用vector的即可:

	// 迭代器区间构造函数
	template <class InputIterator>
	Priority_queue(InputIterator first, InputIterator last)
		:_con(first, last)
	{
	}

1.2、优先级队列的插入push

既然有限就队列的底层是一个堆,那它的插入也和堆一样,建堆使用的是向上调整算法,我们先来简单的回顾一下:
以小堆为例:
在这里插入图片描述

建小堆我们的向上调整算法需要做的就是,从指定的孩子节点位置开始,和父节点比较,判断是否满足堆结构,如果不满足,就交换父子节点,然后原来的父节点编程子节点,再次进行上述操作,直到满足堆结构为止。

以下是代码实现:

// 向上调整算法
void adjust_up(size_t child) {
	size_t parent = (child - 1) / 2;
	while (child > 0) {
		if (_cmp(_con[parent], _con[child])) {
			swap(_con[parent], _con[child]);
		}
		else {
			break;
		}
		child = parent;
		parent = (child - 1) / 2;
	}
}

然后我们的插入接口就像堆一样,每插入一个节点就向上调整一次即可:

// 插入
void push(const T& x) {
	_con.push_back(x);
	adjust_up(_con.size() - 1);
}

1.3、优先级队列的删除(删除堆顶元素)

删除pop使用的是向下调整算法,我们还是先来简单的回顾一下:
还是以小堆为例:
在这里插入图片描述
如上图,向下调整算法我们要做的是找到两个孩子中较小的,然后与父节点比较大小,如果父节点大于子节点就执行交换,然后原来的子节点成为新的父节点,再次进行上述步骤。直到符合堆的结构。

这是代码实现:

// 向下调整算法
void adjust_down(size_t parent) {
	size_t child = 2 * parent + 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[parent], _con[child]);
		}
		parent = child;
		child = 2 * parent + 1;
	}
}

我们堆中的删除是使用了一个巧妙的方式来节省开销,先将顶部的元素与最后一个元素位置互换,然后再删除最后一个元素,再堆顶部元素进行向下调整:

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

1.4、获取堆顶元素

其实堆最主要的接口就上面这两个,实现了上面的两个接口,其他的接口就很简单了,简直不用动脑。

获取顶部元素,我们直接返回_con.front()即可:

	 // 顶部元素
	T& top() {
		return _con.front();
	}

1.5、判断是否为空

因为vector本身就又判空接口,所以没的说,也是复用:

// 判空
bool empty() {
	return _con.empty();
}

1.6、优先级队列的大小size

这个也一样:

// 长度
size_t size() {
	return _con.size();
}

测试结果:
小堆:
在这里插入图片描述
大堆:
在这里插入图片描述

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

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

相关文章

【蓝桥每日一题]-快速幂,倍增,滑动窗口(保姆级教程 篇1) #麦森数 #青蛙跳

之前是考试准备&#xff0c;所以有几天没更新&#xff0c;今天开始继续更新 目录 快速幂模板 题目&#xff1a;麦森数 思路&#xff1a; 题目&#xff1a;青蛙跳 思路&#xff1a; 快速幂模板 #include <bits/stdc.h> #define ll long long using namespa…

人工智能与教育:未来的技术融合

人工智能与教育&#xff1a;未来的技术融合 随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;逐渐渗透到我们生活的方方面面&#xff0c;包括教育领域。AI与教育的结合&#xff0c;有望引发一场教育变革&#xff0c;提高教学效果&#xff0c;实现个性化学习&…

ENVI IDL:如何基于气象站点数据进行反距离权重插值?

01 前言 仅仅练习&#xff0c;大可使用ArcGIS或者已经封装好的python模块进行插值&#xff0c;此处仅仅从底层理解如何从公式和代码理解反距离权重插值的过程&#xff0c;从而更深刻的理解IDL的使用和插值的理解。 02 函数说明 2.1 Read_CSV()函数 官方语法如下&#xff1a…

JVM虚拟机:垃圾回收器之Parallel Old(老年代)

本文重点 本文将学习老年代的另外一种垃圾回收器Parallel Old(PO)&#xff0c;这是一种用于老年代的并行化垃圾回收器&#xff0c;它使用标记整理算法进行垃圾回收。 历史 在1.6之前&#xff0c;新生代使用Parallel Scavenge只能搭配老年代的Serial Old收集器&#xff0c;而…

蓝桥杯算法双周赛心得——被替换的身份证(分类讨论)

大家好&#xff0c;我是晴天学长&#xff0c;分类讨论一定要细节啊&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。&#x1f4aa;&#x1f4aa;&#x1f4aa; 1) .被替换的身份证 2) .算法思路 假设一方获胜 1.接受数据 2.假设潜梦醒 无非就是&am…

Image透明度点击简述以及Unity2019之后存在无法点击的BUG修复

前言 自Unity2019之后Unity将UGUI模块从内置库修改成了通过PackageManger引入的方式。Image就来源于com.unity.modules.imgui模块。其实代码大体代码跟2018是一致的&#xff0c;但是还是有些细微差别&#xff0c;Image透明度点击不命中就是2019之后才有的问题&#xff0c;2018…

JVM虚拟机:垃圾回收器之CMS(老年代)

本文重点 在前面的课程中我们学习了Serial和PO垃圾回收器,本文将学习一种新的在老年代使用的垃圾回收器CMS。 特点 CMS收集器是一种以获取最短回收停顿时间为目标的收集器(还是会有短暂的STW),适合互联网或者B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望…

SQL必知会(二)-SQL查询篇(6)-创建计算字段

第7课、创建计算字段 1&#xff09;拼接字段 需求&#xff1a;检索Vendors 表包含供应商的名称和地址的所有信息&#xff0c;返回结果需要把地址括号起来。 SELECT vend_name ( vend_country ) FROM Vendors ORDER BY vend_name;-- 以下例子与上面例子相同工作 SELECT ve…

Rust的崛起:现代必备编程语言,是时候该考虑加入学习了

在不断变化的编程环境中&#xff0c;新的语言和框架如雨后春笋般涌现&#xff0c;需要一个真正强大且设计良好的工具才能脱颖而出。在这些工具中&#xff0c;Rust 已成为效率、安全性和性能的灯塔。从它作为 Mozilla 的一个副项目到它在软件行业中不可否认的增长&#xff0c;Ru…

PHP中传值与引用的区别

在PHP中&#xff0c;变量的传递方式主要分为传值和传引用两种。这两种方式在操作中有一些重要的区别&#xff0c;影响着变量在函数调用或赋值操作中的表现。下面详细解释一下这两种传递方式的区别。 传值&#xff08;By Value&#xff09; 传值是指将变量的值复制一份传递给函…

Python环境安装、Pycharm开发工具安装(IDE)

Python下载 Python官网 Python安装 Python安装成功 Pycharm集成开发工具下载&#xff08;IDE&#xff09; PC集成开发工具 Pycharm集成开发工具安装&#xff08;IDE&#xff09; 安装完成 添加环境变量&#xff08;前面勾选了Path不用配置&#xff09; &#xff08;1&…

个人技术支持

本人目前从事 cnc 自动编程相关职业&#xff0c;主要还是做上位机开发&#xff0c;2021年之前一直从事 Unity3d 开发&#xff0c;本来也是个游戏程序员&#xff0c;后面也是大环境不好&#xff0c;改做了上位机开发&#xff0c;没想到上位机行业现在也是这么不好找工作。 最近…

java 类和对象 (图文搭配,万字详解!!)

关于java类和对象&#xff0c;我们要掌握几个重点&#xff01; 1.类的定义方式以及对象的实例化 2.类中的成员变量和成员方法的使用 3.对象的整个初始化过程 4.封装特性 5.代码块 目录 一、面向对象的初步认识 1.1 什么是面向对象 1.2 面向对象与面向过程 1.2.1传统洗…

【电路笔记】-节点电压分析和网状电流分析

节点电压分析和网状电流分析 文章目录 节点电压分析和网状电流分析1、节点电压分析1.1 概述1.2 示例 2、网格电流分析2.1 概述2.2 示例 3、总结 正如我们在上一篇介绍电路分析基本定律的文章中所看到的&#xff0c;基尔霍夫电路定律 (KCL) 是计算任何电路中未知电压和电流的强大…

[蓝桥杯复盘] 第 3 场双周赛20231111

[蓝桥杯复盘] 第 3 场双周赛20231111 总结深秋的苹果1. 题目描述2. 思路分析3. 代码实现 鲜花之海1. 题目描述2. 思路分析3. 代码实现 斐波拉契跳跃2. 思路分析3. 代码实现 星石传送阵2. 思路分析3. 代码实现 六、参考链接 总结 做了后4题。https://www.lanqiao.cn/oj-contes…

SqlServerAgent当前未运行,因此无法将此操作通知他。错误:22022

问题&#xff1a;SqlServerAgent当前未运行&#xff0c;因此无法将此操作通知他。&#xff08;Microsoft SQL Server&#xff0c;错误&#xff1a;22022&#xff09; 解决方案&#xff1a; 1.Win R 输入 services.msc 后&#xff0c;点击【确定】按钮 2.选择SQL Server 代理…

ObjectArx动态加载及卸载自定义菜单

上节中我们介绍了如何制作自定义菜单即cuix文件&#xff1a;给CAD中添加自定义菜单CUIX-CSDN博客https://blog.csdn.net/qianlixiaomage/article/details/134349794在此基础上&#xff0c;我们开发时通常需要在ObjectArx程序中进行动态的添加或者删除cuix菜单。 创建ObjectArx…

php性能追踪与分析

PHP扩展下载&#xff1a;https://pecl.php.net/package/xhprof php.ini配置 [xhprof] extensionxhprof xhprof.output_dir/temp/xhprof auto_prepend_file /temp/inject_xhprof.php if(php_sapi_name() cli) {return; }$xhprof_config[enabled]1;if(!empty($xhprof_config…

数据挖掘:分类,聚类,关联关系,回归

数据挖掘&#xff1a; 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle&#xff0c;尤其sql要学&…

C语言求解猴子分桃问题

题目&#xff1a; 海滩上有一堆桃子&#xff0c;五只猴子来分。第一只猴子把这堆桃子凭据分为五份&#xff0c;多了 一个&#xff0c;这只猴子把多的一个扔入海中&#xff0c;拿走了一份。第二只猴子把剩下的桃子又平均分 成五份&#xff0c;又多了一个&#xff0c;它同样把多…