【C++STL详解(十)】--------priority_queue的模拟实现

目录

前言

一、堆的向上调整算法

二、堆的向下调整算法

三、优先队列模拟实现

Ⅰ、接口总览

Ⅱ、各个接口实现

1.构造函数

2.仿函数

3.向上调整

4.向下调整

5.其余接口

Ⅲ、完成代码


前言

上节内容我们简单的介绍了关于priority_queue的使用内容,我们明白了它的默认容器是vector,以及优先队列实际上默认就是个大堆等相关知识,那么接下来就来看看底层的模拟实现究竟是什么样的!但在此之前先简单介绍两个堆算法,向上调整和向下调整算法!

一、堆的向上调整算法

我们在数据结构中都知道堆在物理空间上是采用数组去存储的,但是呢在逻辑上我们可以将其看作一棵完全二叉树,形如:

以上这个是大堆,小堆反之,下面以大堆为例介绍向上调整算法!

向上调整:

①在大堆的末尾插入一个数据,然后和其父亲结点去比较!

②如果大于父亲结点,那就和父亲结点交换位置,并更新父亲结点,直到比父亲结点小;如果比父亲结点小,那就停止交换!此时就是大堆了!

小堆过程相反!!把小的向上调即可

注意:在数据结构的树与二叉树中提到过父亲结点和孩子结点的下标关系

左孩子=父亲*2+1;

右孩子=父亲*2+2;

例如,在上述堆中插入一个数据77。过程如下:

和其父亲结点比较发现,比父亲结点大,那就交换!

在去新的父节点比较,即和66相比,比它大,交换!

到这里就调整完毕了,此时就是个大堆了!

具体代码如下:

//建大堆
void AdjustUp(vector<int>& v1 int child)
{int parent = (child - 1) / 2;//通过父子下标关系得出while (child > 0){if (v[child] > v[parent]){swap(v[child], v[parent]);//交换child = parent;//更新孩子parent = (child - 1) / 2;//更新父亲}//至此已成堆else{break;}}
}

二、堆的向下调整算法

同样还是以大堆为例,进行向下调整,但是这里有个前提:一定要保证左右子树是一个大堆,才可以进行向下调整!建小堆,也是要保证左右子树都是小堆才可以!

向下调整:

①从堆顶向下,先选出当前父节点的左右孩子中的最大节点,然后再用当前节点去和最大节点的比较!

②如果父节点小于最大孩子节点,那就交换父子节点,并重新更新父子节点;如果大于最大节点,那就不能交换,此时就是大堆了!

小堆就是相反的,实际就是把大的向下调!大堆就是把小的向下调!!

例如,上图先找出左右孩子中的最大节点,即77作为最大孩子。22与77相比,22比77小,那就交换!

再重复上述步骤,因为只有33这个节点,并且22小于33,即父亲小于孩子,那就交换!

至此已经来到了末尾,交换结束,此时的结构就是大堆!!!!

具体实现代码如下:

void Adjustdown(vector<int>& a, int size, int parent)
{int child = parent * 2 + 1;//左孩子while (child<size){//找左右孩子哪个大,把大给childif (child + 1 < size && a[child + 1] > a[child]){child = child + 1;}//比较孩子和父亲if (a[child] > a[parent]){swap(a[child], a[parent]);parent=child;//更新父亲child = parent * 2 + 1;//更新孩子}//至此已成大堆else{break;}}
}

总结一下:向上调整就是拿孩子去比父亲,所以参数得是孩子的;向下调整实际就是拿父亲去比孩子,所以参数得父亲的下标!!

注意:实际应用中,大多数都是采用向下调整建堆,因为时间复杂度为O(N),而向上调整时间复杂度为O(N*logN);

三、优先队列模拟实现

有上面两个算法的铺垫,接下来的模拟实现就简单很多了!

Ⅰ、接口总览

#include<vector>namespace Pq
{//仿函数,控制比较方式template <class T>class less{public:bool operator()(const T& x, const T& y);};template <class T>class greater{public:bool operator()(const T& x, const T& y);};template <class T, class Container = vector<T>, class Compare = less<T> >class priority_queue{public://构造空队列priority_queue();//迭代器区间构造队列template <class InputIterator>priority_queue(InputIterator first, InputIterator last);void push(const T& x);void pop();bool empty() const;size_t size() const;const T& top() const;private:Container c;Compare comp;//向上调整算法void AdjustUp(size_t child);//向下调整算法void Adjustdown(size_t parent);};};

注意:一样的,模拟实现,毕竟只是模拟,一定要记得在自己的空间里面去模拟哦!同时我们这里为了更真实的去模拟,我们将向上调整和向下调整设置为私有函数!!!因为平时去调用时,根本就看不见这两个函数,是吧哥们!

Ⅱ、各个接口实现

1.构造函数

  • 构造空队列
//构造空队列
priority_queue():c()
{}
  • 迭代器区间初始化

写法一:

//迭代器区间构造队列
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{while (first != last){c.push_back(*first);first++;}//插入数据应该要继续保持堆结构//这里是将一堆已经存在的数据进行建堆//向下调整建大堆时间复杂度更低for (int i = (c.size() - 1 - 1) / 2; i >= 0; i--){Adjustdown(i);}
}

这样的写法实际和vector、list等模拟实现相类似,都是通过尾插操作去实现的!但是要注意一点,优先队列就是个堆结构,插入数据时应该要调整它的结构,前面也说过向下调整时间复杂度低,所以这里采用向下调整算法建堆,但是一定要注意向下调整是有前提的必须要求左右子树都是一个大堆(或者小堆),因此我们应该从最后一个非叶子结点开始去调整,也就是最后一个父结点开始向下调整!!!!

写法二:

template <class InputIterator>
priority_queue(InputIterator first, InputIterator last):c(first,last)
{for (int i = (c.size() - 1 - 1) / 2; i >= 0; i--){Adjustdown(i);}
}

这个写法就是利用优先队列实际上是一个容器适配器,也就是说用别人的东西去创造自己,也就是它的成员变量实际上就是对应容器,相当与一个自定义类型,那么对于自定义类型,他就会去调用自己的构造函数完成初始化工作!

例如:当传进来的是vector容器时,优先队列里面的成员变量就是vector示例化出来的对象,对这个对象进行初始化工作,实际上就是在调用vector的默认成员函数完成构造!!!

2.仿函数

这里在前面的优先队列介绍中就有涉及,要注意一点仿函数可以控制比较逻辑,在优先队列的底层,大堆(less)实际上是用<比较,小堆(greater)实际上是用>比较!

//大堆,<比较
template <class T>
class less
{public:bool operator()(const T& x, const T& y){return x < y;}
};//小堆,>比较
template <class T>
class greater
{public:bool operator()(const T& x, const T& y){return x > y;}
};

3.向上调整

//向上调整算法(大堆为例)
void AdjustUp(size_t child)
{size_t parent = (child - 1) / 2;while (child > 0){//if (_con[parent] < _con[child])if (comp(c[parent], c[child])){swap(c[parent], c[child]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

注意:整体逻辑和上面讲到的差不多,只不过这里的比较逻辑采用了仿函数,comp实际上是仿函数实例化出来的对象,在成员变量里面了!

4.向下调整

 //向下调整算法(默认大堆)void Adjustdown(size_t parent){size_t child = 2 * parent + 1;while (child < c.size()){if (child + 1 < c.size() && comp(c[child], c[child + 1]))//仿函数控制比较逻辑{child = child + 1;}//用仿函数                if (comp(c[parent], c[child])){swap(c[parent], c[child]);parent = child;child = 2 * parent + 1;}//至此已成大堆else{break;}}}

5.其余接口

void push(const T& x)
{c.push_back(x);AdjustUp(c.size() - 1);//最后一个元素向上调整
}void pop()
{swap(c[0], c[c.size() - 1]);c.pop_back();//在使用向下调整堆结构Adjustdown(0);
}bool empty() const
{return c.empty();
}size_t size() const
{return c.size();
}const T& top() const
{return c[0];
}

需要注意的是堆的删除操作(pop),它实际上就是先把堆顶元素与最后一个元素交换,然后在把最后一个元素不看做堆的元素,也就是删除,最后在采用向下调整堆结构!!

例如:

Ⅲ、完成代码

#pragma once
#include<vector>namespace Pq
{template <class T>class less{public:bool operator()(const T& x, const T& y){return x < y;}};//建小堆,>比较template <class T>class greater{public:bool operator()(const T& x, const T& y){return x > y;}};template <class T, class Container = vector<T>, class Compare = less<T> >class priority_queue{public://构造空队列priority_queue():c(){}//迭代器区间构造队列template <class InputIterator>priority_queue(InputIterator first, InputIterator last){while (first != last){c.push_back(*first);first++;}for (int i = (c.size() - 1 - 1) / 2; i >= 0; i--){Adjustdown(i);}}void push(const T& x){c.push_back(x);AdjustUp(c.size() - 1);//最后一个元素向上调整}void pop(){swap(c[0], c[c.size() - 1]);c.pop_back();Adjustdown(0);}bool empty() const{return c.empty();}size_t size() const{return c.size();}const T& top() const{return c[0];}private:Container c;Compare comp;void AdjustUp(size_t child){size_t parent = (child - 1) / 2;while (child > 0){//if (_con[parent] < _con[child])if (comp(c[parent], c[child])){swap(c[parent], c[child]);child = parent;parent = (child - 1) / 2;}else{break;}}}void Adjustdown(size_t parent){size_t child = 2 * parent + 1;while (child < c.size()){if (child + 1 < c.size() && comp(c[child], c[child + 1])){child = child + 1;}          if (comp(c[parent], c[child])){swap(c[parent], c[child]);parent = child;child = 2 * parent + 1;}else{break;}}}};};

今天就分享到这里,如果对你有帮助,请多多支持,你的支持是我更新的动力!!

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

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

相关文章

Baidu Comate 编程插件:提升开发效率的利器

文章目录 引言简介目的 Baidu Comate插件概述定义与功能市场现状竞品分析 安装与配置VsCode 安装&#xff1a;注意事项 版本选择 核心特性详解功能介绍代码生成实时续写错误纠正 使用体验体验地址 引言 简介 基于文心大模型&#xff0c;结合百度积累多年的编程现场大数据和外…

PDF批量编辑:PDF转HTML批量操作技巧,提升文档格式转换效率

在数字化办公日益普及的今天&#xff0c;PDF&#xff08;Portable Document Format&#xff09;作为一种跨平台的文件格式&#xff0c;广泛应用于各种文档的存储和传输。然而&#xff0c;PDF文件的不可编辑性使得在某些情况下&#xff0c;我们需要将其转换为HTML格式以便更好地…

【国产SSL】哪家SSL证书可以保证数据不出境,是在国内验签

随着网络安全的重视&#xff0c;网站安装SSL证书已经是标配了。但是为什么目前常见的SSL证书都是国外的&#xff1f;数据受国外掌控&#xff0c;安全吗&#xff1f;那么哪家国产品牌是可以保证数据不出境的呢&#xff1f; 为什么目前常见的SSL证书都是国外的&#xff1f; 原因…

Java苍穹外卖05-订单状态定时处理-数据统计-导出excel

一、订单状态定时处理 1.Spring Task ①介绍 应用场景&#xff1a; ②cron表达式 https://cron.qqe2.com/ ③入门案例 2.需求分析 3.代码开发 每一分钟检查是否存在超时15分钟的订单 每天凌晨一点处理上一条处于派送中的订单 mapper&#xff1a; 二、来单提醒、客户催单 1…

手撕C语言题典——移除链表元素(单链表)

目录 前言 一.思路 1&#xff09;遍历原链表&#xff0c;找到值为 val 的节点并释放 2&#xff09;创建新链表 二.代码实现 1)大胆去try一下思路 2&#xff09;竟然报错了&#xff1f;&#xff01; 3&#xff09;完善之后的成品代码 搭配食用更佳哦~~ 数据结构之单…

CellChat包文献介绍

Inference and analysis of cell-cell communication using CellChat - PubMed (nih.gov) 目录 在线数据 摘要 基础介绍 分析结果 1&#xff0c;概述 2&#xff0c;识别预测通路 3&#xff0c;连续的信号转导 4&#xff0c;预测空间共定位细胞群之间的关键信号转导事件…

Al Agent:开启智能化未来的关键角色,让机器更智能的为我们服务

文章目录 &#x1f680;Al Agent是什么&#x1f4d5;Al Agent的工作原理与技术&#x1f4aa;Al Agent应用领域&#x1f680;智能家居应用&#x1f308;医疗健康领域⭐金融服务行业&#x1f302;交通运输管理&#x1f3ac;教育培训应用 &#x1f512;Al Agent优势与挑战✊Al Age…

码题杯 世界警察 思想:双指针

https://www.matiji.net/exam/brushquestion/4/4446/16A92C42378232DEB56179D9C70DC45C 双指针 思路是这样的&#xff0c;首先r指针向右走&#xff0c;如果r指针遇到了和l指针一样的&#xff0c;那么l指针就&#xff0c;一直加到r指针的位置&#xff0c;此时a[l]a[r]&#xff0…

Flutter 首次亮相 Google Cloud Next 大会

作者 / Kelvin Boateng Flutter 团队在近期首次参加了 Google Cloud Next 大会&#xff0c;这意味着 Flutter 在开发社区中的影响力正在日益增长。 Google Cloud Next https://cloud.withgoogle.com/next 我们与 Google Cloud、Firebase、Very Good Ventures 和 Serverpod 的团…

双向链表(详解)

在单链表专题中我们提到链表的分类&#xff0c;其中提到了带头双向循环链表&#xff0c;今天小编将详细讲下双向链表。 话不多说&#xff0c;直接上货。 1.双向链表的结构 带头双向循环链表 注意 这几的“带头”跟前面我们说的“头节点”是两个概念&#xff0c;实际前面的在…

【大学物理】东北大学-马文蔚听课笔记

4.1刚体的定轴转动_哔哩哔哩_bilibili 此笔记为课堂学习笔记~ 4.1刚体的定轴转动 基本教学要求 什么时刚体呢&#xff1f; 研究刚体运动切口 平动&#xff1a;刚体中所有的点的运动轨迹都完全相同。 转动&#xff1a;分为&#xffe5;定轴转动和非定轴转动 刚体转动的角速度…

【Unity 2D物理系统:触发】

在Unity的2D物理系统中&#xff0c;触发器&#xff08;Trigger&#xff09;是一种特殊的碰撞检测机制&#xff0c;它允许开发者检测到物体进入或离开特定区域时发生的动作&#xff0c;而不会影响物体的实际物理运动。触发器通常用于非物理交互的场景&#xff0c;如检测玩家进入…

vue3 antd-vue 超简单方式实现a-table跨页勾选

一、效果如下&#xff1a; 第一页勾选了2&#xff0c; 3&#xff0c; 4 翻到第三页勾选24&#xff0c; 25 回显&#xff0c;如比返回第一页的时候触发分页改变&#xff0c; 在映射中的第一页的数据给到a-table绑定的state.selectedRowKeys即可&#xff0c;如下方法 二、勾选思路…

Netty底层数据交互源码分析

文章目录 1. 前题回顾2. 主线流程源码分析3. Netty底层的零拷贝4. ByteBuf内存池设计 书接上文 1. 前题回顾 上一篇博客我们分析了Netty服务端启动的底层原理&#xff0c;主要就是将EventLoop里面的线程注册到了Select中&#xff0c;然后调用select方法监听客户端连接&#xf…

深入了解模拟和存根:提高单元测试质量的关键技术

一、引言 在进行单元测试时&#xff0c;我们经常会遇到对外部资源的依赖&#xff0c;如数据库、网络接口等。模拟&#xff08;Mocking&#xff09;和存根&#xff08;Stubbing&#xff09;是两种帮助我们模拟这些外部资源&#xff0c;使我们能够在隔离环境中测试单元的方法。在…

分布式任务调度框架xxl-job使用手册

官网地址和文档地址&#xff1a;https://www.xuxueli.com/xxl-job/ 一、快速入门 1.1 下载源码 https://github.com/xuxueli/xxl-job https://gitee.com/xuxueli0323/xxl-job 下载完成后有以下模块 1.2 初始化数据库 官方指定mysql8.0&#xff0c;但我是mysql5.7 执行/xxl…

PyQt6--Python桌面开发(6.QLineEdit单行文本框)

QLineEdit单行文本框 import sys import time from PyQt6.QtGui import QValidator,QIntValidator from PyQt6.QtWidgets import QApplication,QLabel,QLineEdit from PyQt6 import uicif __name__ __main__:appQApplication(sys.argv)uiuic.loadUi("./QLine单行文本框.u…

Qt 6.7功能介绍

Qt 6.7为我们所有喜欢在构建现代应用程序和用户体验时获得乐趣的人提供了许多大大小小的改进。一些新增内容作为技术预览发布&#xff0c;接下来我们就一起来看看吧&#xff1a; 将C20与Qt一起使用 对于许多编译器工具链来说&#xff0c;C20仍然是可选的和实验性的&#xff0c;…

台服dnf局域网搭建,学习用笔记

台服dnf局域网搭建 前置条件虚拟机初始化上传安装脚本以及其他文件至虚拟机密钥publickey.pem客户端配置如果IP地址填写有误&#xff0c;批量修改IP地址 前置条件 安装有vmvarecentos7.6镜像&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/centos-vault/7.6.1810/isos/x86…

Python注意事项【自我维护版】

各位大佬好 &#xff0c;这里是阿川的博客 &#xff0c; 祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 本篇博客在之前的博客上进行的维护 创建Python…