【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,一经查实,立即删除!

相关文章

秒杀系统架构设计-01

前言 在当今互联网技术快速发展的背景下&#xff0c;电商平台的秒杀活动成为了一个热门话题。特别是对于技术人员而言&#xff0c;如何设计一个高效、可靠的秒杀系统架构&#xff0c;已经成为了面试中的常见问题。这篇博文将围绕秒杀系统架构设计的大纲和授课思路&#xff0c;…

【TypeScript高级类型简介以及使用方法】

TypeScript 的高级类型提供了更复杂的类型系统&#xff0c;允许开发者更精确地描述和约束代码中的数据类型。以下是几种常用的 TypeScript 高级类型及其简介和使用方法&#xff1a; 交叉类型&#xff08;Intersection Types&#xff09; 交叉类型是将多个类型合并为一个类型&…

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

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

三维dp,LeetCode 741. 摘樱桃

一、题目 1、题目描述 给你一个 n x n 的网格 grid &#xff0c;代表一块樱桃地&#xff0c;每个格子由以下三种数字的一种来表示&#xff1a; 0 表示这个格子是空的&#xff0c;所以你可以穿过它。1 表示这个格子里装着一个樱桃&#xff0c;你可以摘到樱桃然后穿过它。-1 表示…

设计一个people类 如何输入用户自定义个数的对象?

题目: 设计一个People 类&#xff0c;该类的数据成员有姓名、年龄、身高、体重和人数&#xff0c;其中人数为静态数据成员&#xff0c;成员函数有构造函数、显示和显示人数。其中构造函数由参数姓名、年龄、身高和体重来构造对象&#xff1b;显示函数用于显示人的姓名、年龄、…

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

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

蓝桥杯备赛1.统计单词数

题目链接&#xff1a;P1308 [NOIP2011 普及组] 统计单词数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include<bits/stdc.h> #define int long long #define endl \n const int N 1e510; using namespace std; int a[N]; signed main() {std::ios::sync_with_st…

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

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

您的计算机已被360勒索病毒感染?恢复您的数据的方法在这里!

引言 .360勒索病毒&#xff0c;作为BeijingCrypt勒索病毒家族的一员&#xff0c;因其独特的加密技术和狡猾的传播方式&#xff0c;已成为网络安全领域的重大威胁。它不仅能加密用户的重要文件&#xff0c;使文件无法被正常访问和使用&#xff0c;还可能进一步破坏系统&#xf…

ssh连接服务器教程,账号密码登陆和通过密钥直接登陆

ssh如何连接服务器&#xff1f; 两种连接方式 通过账号密码登陆通过密钥直接登陆 通过账号密码登陆 在终端使用命令登陆&#xff1a;ssh [服务器用户名][服务器ip或服务器域名]之后输入服务器用户密码即可登陆 通过密钥直接登陆 首先在本地创建自己的私钥公钥&#xff0c…

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

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

WPF (Windows Presentation Foundation) 中 Attribute(属性)和 Property(属性)

在 WPF (Windows Presentation Foundation) 中&#xff0c;Attribute&#xff08;属性&#xff09;和 Property&#xff08;属性&#xff09;是两个相关但不同的概念。 Attribute&#xff08;属性&#xff09;是一种元数据&#xff0c;用于给类型、成员或其他代码元素添加附加…

手撕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;预测空间共定位细胞群之间的关键信号转导事件…

算法随想录第四天打卡|24. 两两交换链表中的节点,19.删除链表的倒数第N个节点,面试题 02.07. 链表相交 ,142.环形链表II

24. 两两交换链表中的节点 用虚拟头结点&#xff0c;这样会方便很多。 本题链表操作就比较复杂了&#xff0c;建议大家先看视频&#xff0c;视频里我讲解了注意事项&#xff0c;为什么需要temp保存临时节点。 题目链接/文章讲解/视频讲解&#xff1a; 代码随想录 Python # De…

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

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

mdio 的匹配与探测

关键结构体定义 struct phy_device { struct mdio_device mdio; } struct phy_driver { struct mdio_driver_common mdiodrv; } struct mdio_driver_common { struct device_driver driver; int flags; }; 1.1 总线匹配函数 struct bus_type mdio_bus_type { …

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

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;实际前面的在…