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

【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.hqwc.cn/news/169355.html

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

相关文章

01:2440----点灯大师

目录 一:点亮一个LED 1:原理图 2:寄存器 3:2440的框架和启动过程 A:框架 B:启动过程 4:代码 5:ARM知识补充 6:c语言和汇编的应用 A:代码 B:分析汇编语言 C:内存空间 7:内部机制 二:点亮2个灯 三:流水灯 四:按键控制LED 1:原理图 2:寄存器配置 3:代码 一:点…

python实现炒股自动化,个人账户无门槛量化交易的开始

本篇作为系列教程的引子&#xff0c;对股票量化程序化自动交易感兴趣的朋友可以关注我&#xff0c;现在只是个粗略计划&#xff0c;后续会根据需要重新调整&#xff0c;并陆续添加内容。 股票量化程序化自动交易接口 很多人在找股票个人账户实现程序化自动交易的接口&#xff0…

Django——orm模块创建表关系

django orm中如何创建表关系 1. 表关系分析 表与表之间的关系: 一对多 多对多 一对一 没有关系 判断表关系的方法: 换位思考用4张表举例: 图书表 出版社表 作者表 作者详情表图书和出版社是一对多的关系 外键字段建在多的那一方图书和作者是多对多的关系 需要创建第三张表来…

(离散数学)命题及命题的真值

答案&#xff1a; &#xff08;5&#xff09;不是命题&#xff0c;因为真值不止一个 &#xff08;6&#xff09;不是命题&#xff0c;因为不是陈述句 &#xff08;7&#xff09;不是命题&#xff0c;因为不是陈述句 &#xff08;8&#xff09;不是命题&#xff0c;真值不唯一

Qt 自定义按钮 区分点按与长按信号,适配触摸事件

Qt 自定义按钮 区分点按与长按信号 适配触摸事件 效果 使用示例 // 点按connect(ui.btnLeft, &JogButton::stepclicked, this, &MainWindow::btnLeft_clicked);// 长按开始connect(ui.btnLeft, &JogButton::continueOn, this, &MainWindow::slotJogLeftOn);//…

Linux 基于 LVM 逻辑卷的磁盘管理【简明教程】

一、传统磁盘管理的弊端 传统的磁盘管理&#xff1a;使用MBR先对硬盘分区&#xff0c;然后对分区进行文件系统的格式化最后再将该分区挂载上去。 传统的磁盘管理当分区没有空间使用进行扩展时&#xff0c;操作比较麻烦。分区使用空间已经满了&#xff0c;不再够用了&#xff…

Qt 事件循环

引出 UI程序之所叫UI程序&#xff0c;是因为需要与用户有交互&#xff0c;用户交互一般是通过鼠标键盘等的输入设备&#xff0c;那UI程序就需要有能随时响应用户交互的能力。 一个C程序的main函数大概是下面这样&#xff1a; int main() {...return 0; } 我们如何使程序能随…

深度学习之基于Pytorch框架的MNIST手写数字识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 MNIST是一个手写数字识别的数据集&#xff0c;是深度学习中最常用的数据集之一。基于Pytorch框架的MNIST手写数字识…

Linux - 基础IO(重定向 - 重定向模拟实现 - shell 当中的 重定向)- 下篇

前言 上一篇博客当中&#xff0c;我们对 文件 在操作系统当中是 如何就管理的&#xff0c;这个问题做了 详细描述&#xff0c;本篇博客将基于上篇 博客当中的内容进行 阐述&#xff0c;如有疑问&#xff0c;请参考上篇博客&#xff1a; Linux - 基础IO&#xff08;Linux 当中…

Git之分支与版本

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《Spring与Mybatis集成整合》《Vue.js使用》 ⛺️ 越努力 &#xff0c;越幸运。 1.开发测试上线git的使用 1.1. 环境讲述 当软件从开发到正式环境部署的过程中&#xff0c;不同环境的作用…

adb and 软件架构笔记

Native Service&#xff0c;这是Android系统里的一种特色&#xff0c;就是通过C或是C代码写出来的&#xff0c;供Java进行远程调用的Remote Service&#xff0c;因为C/C代码生成的是Native代码&#xff08;机器代码&#xff09;&#xff0c;于是叫Native Service。 native服务…

可视化 | echarts中国地图散点图

改编自echarts添加地图散点 &#x1f4da;改编点 roam: false&#xff1a;不允许放缩拖动 地图颜色修改 geo: {show: true,top: 15%,map: name,label: {normal: {show: false},emphasis: {show: true,color: "#fff",}},roam: false,itemStyle: {normal: {areaColor…