【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://xiahunao.cn/news/3029475.html

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

相关文章

【数据结构】手把手带你玩转线性表

前言&#xff1a; 哈喽大家好&#xff0c;我是野生的编程萌新&#xff0c;首先感谢大家的观看。数据结构的学习者大多有这样的想法&#xff1a;数据结构很重要&#xff0c;一定要学好&#xff0c;但数据结构比较抽象&#xff0c;有些算法理解起来很困难&#xff0c;学的很累。我…

crontab开启定时任务

linux上面可以使用crontab -e配置定时任务,但是一般需求进行一些配置才能使用,默认如下: crontab开启定时任务&#xff1a; 1.输入select-editor 2.选择 2. /usr/bin/vim.basic 有时候不需要第一步直接输入2就可以了,如下图所示 此时就可以在里面配置我们想要执行的定时任务…

vue3实现动态表格

vue3结合element-plus实现动态表格&#xff0c;可添加、删除、对单行数据判断。 实现效果&#xff1a;查看源代码 实现代码&#xff1a; <div class"arrTable-Box"><el-table :data"tableData" border max-height"250"><el-t…

控制台打印空数组展开有数据

控制台打印空数组展开有数据 控制台显示: 代码如下: export const getDict1 = (dictCode) => {let list = []queryDict({dictCode }

nginx配置文件和配置命令详解案例

一.nginx.conf配置结构 1.1配置结构图 1.2 nginx中配置nginx.conf配置内容 #user nobody; user root; # 表示worker进程是有root用户去执行的 worker_processes 2; events {# 默认使用epolluse epoll;# 每个worker链接最大数据worker_connections 1024; } http {include …

直播产业赋能数字经济蓬勃发展!成都东部集团有限公司莅临天府锋巢直播产业基地考察交流

2024年4月25日&#xff0c;天府锋巢直播产业基地迎来了一次重要的考察交流。成都东部集团有限公司产业部副部长高文婷、集团产业部主管罗中婧亲临基地&#xff0c;与天府锋巢直播产业基地的招商总负责人姜国东等基地代表进行了深入的交流和探讨。 姜国东热情接待了来访的考察团…

芝加哥量子曼哈顿项目:200 亿美元的量子计算园区

内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 文丨王珩 排版丨沛贤 深度好文&#xff1a;1000字丨5分钟阅读 摘要&#xff1a;芝加哥商业媒体称&#xff0c;伊利诺伊州政府正在大力推动耗资200亿美元、占地150英亩的芝加哥量子计算园区建设…

Windows11 同时安装jdk8和jdk17 可切换

Windows11 同时安装jdk8和jdk17 可切换 死忠于JDK8的码农们&#xff0c;可能不得不做出一些改变的 因为在springboot3最低也是只能用17 并且最近如果创建springboot项目的时候&#xff0c;你会发现&#xff0c;最低也是17的 并且&#xff0c;如果使用springcloud开发&#x…

动态IP避坑指南:如何挑选合适的动态代理IP?

在如今的网络环境中&#xff0c;使用动态IP代理成为实现隐私保护、访问受限内容和提高网络效率的一种常见方式&#xff0c;选择合适的国外动态IP代理可以让我们的业务处理事半功倍。面对市面上琳琅满目的选择&#xff0c;如何挑选购买适合自己的动态IP代理服务呢&#xff1f;在…

通用产品发布解决方案(家居分类表设计以及renren代码生成器的使用)

文章目录 1.商品分类表设计1.需求分析2.数据库表设计1.数据库sunliving_commodity&#xff0c;商品分类表commodity_category2.测试数据 2.代码生成器生成crud1.解压到sunliving下并聚合管理1.解压2.修改sunliving的pom.xml进行聚合管理3.刷新maven报错 parent.relativePath4.将…

深入探索不相交集合:链表表示与加权合并策略的实现

深入探索不相交集合&#xff1a;链表表示与加权合并策略的实现 1. MAKE-SET 操作伪代码C语言实现 2. FIND-SET 操作伪代码C语言实现 3. UNION 操作伪代码C语言实现 4. 集合对象和表对象的属性5. 总结 在本文中&#xff0c;我们将探讨如何使用链表表示和加权合并启发式策略来实现…

如何防止WordPress网站内容被抓取

最近在检查网站服务器的访问日志的时候&#xff0c;发现了大量来自同一个IP地址的的请求&#xff0c;用站长工具分析确认了我的网站内容确实是被他人的网站抓取了&#xff0c;我第一时间联系了对方网站的服务器提供商投诉了该网站&#xff0c;要求对方停止侵权行为&#xff0c;…

求一个B站屏蔽竖屏视频的脚本

求一个B站屏蔽竖屏视频的脚本 现在B站竖屏竖屏越来越多了&#xff0c;手机还好点给我一个按钮&#xff0c;选择不喜欢&#xff0c;但是我一般都用网页版看视屏&#xff0c;网页版不给我选择不喜欢的按钮&#xff0c;目测大概1/4到1/3的视频都是竖屏视频。 目前网页版唯一的进…

H5 处理点击元素高亮、自定义按钮、去除焦点边框

1、设置移动设备上点击元素时出现的高亮颜色 *{-webkit-tap-highlight-color: transparent; }2、如果你想要自定义按钮的样式&#xff0c;你可以使用 -webkit-appearance: none; 来移除按钮的默认样式 .button {-webkit-appearance: none;appearance: none; /* 兼容性更好的通…

转行网络安全的重要建议,助你顺利入门

目录 为什么写这篇文章 为什么我更合适回答这个问题 先问自己3个问题 1.一定要明确自己是否是真喜欢&#xff0c;还是一时好奇。 2.自学的习惯 3.选择网安、攻防这行的目标是什么&#xff1f; 确认无误后&#xff0c;那如何进入这个行业&#xff1f; 1.选择渗透测试集中…

推荐 6 个超好用的 iterm2 zsh 插件

大家好啊&#xff0c;今天给大家分享几个我日常使用的 iterm2 插件&#xff0c;每一个都很有用&#xff0c;希望能给帮助你提高使用命令行的效率&#xff5e; zsh-autosuggestions 插件地址&#xff1a;https://github.com/zsh-users/zsh-autosuggestions 效果展示 当你输入…

鸿蒙开发接口Ability框架:【@ohos.application.Want (Want)】

Want Want模块提供系统的基本通信组件的能力。 说明&#xff1a; 本模块首批接口从API version 8 开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import Want from ohos.application.Want; 开发前请熟悉鸿蒙开发指导文档&#xff1…

【强训笔记】day18

NO.1 思路&#xff1a;双指针模拟。to_string将数字转化为字符。 代码实现&#xff1a; class Solution { public:string compressString(string param) {int left0,right0,nparam.size();string ret;while(right<n){while(right1<n&&param[right]param[right…

TikTok自动评论、回复的脚本怎么制作?

在当今数字化的时代&#xff0c;社交媒体平台如TikTok已经成为人们日常生活的一部分&#xff0c;为了更有效地在TikTok上进行营销或互动&#xff0c;许多用户和企业开始寻找自动化工具&#xff0c;如自动评论和回复的脚本&#xff0c;以节省时间并提高效率。 本文将科普如何制…

2024 年 数维杯(A题)大学生数学建模挑战赛 | 多源机会信号建模| 数学建模完整代码+建模过程全解全析

2024数维杯数学建模A题B题C题思路模型代码&#xff08;开赛后第一时间更新&#xff09;及时留意关注哦 https://mbd.pub/o/bread/ZpWakpdq https://mbd.pub/o/bread/ZpWakpdq 2024数维杯数学建模A题B题C题思路模型代码&#xff08;开赛后第一时间更新&#xff09;及时留意关注…