priority_queue 的模拟实现

news2024/11/18 19:36:34

priority_queue 的底层结构

我们已经学习过栈和队列了,他们都是用一种容器适配出来的。今天我们要学习的 prority_queue 也是一个容器适配器。在 priority_queue 的使用部分我们已经知道想要适配出 priority_queue,这个底层的容器必须有以下接口:

  • empty():检测容器是否为空。
  • size():返回容器中有效元素个数。
  • front():返回容器中第一个元素的引用。
  • push_back():在容器尾部插入元素。

适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。

在学习 C 语言的时候,我们已经学习过堆这种数据结构了,当时我们使用的是用数组作为堆的底层数据结构,因此,我们模拟实现 priority_queue 会使用 vector 作为 priority_queue 的底层数据结构。当然 deque 也是没有问题的,但是不及 vector 高效。因为我们会大量使用 operator[] ,对比下来我们会倾向于选择 vector 来模拟实现 priority_queue

priority_queue 的基本结构实现

实现 priority_queue 的大体思路和模拟实现 stackqueue 差不多。priority_queue 涉及更多的算法和细节处理。

#pragma once
namespace Tchey
{
    template<class T, class Container = vector<T>>
    class priority_queue
    {
    public:

    private:
        Container _con;
    };
}

但是我们这样定义出来的 priority_queue 的模板类好像不对啊!因为库里面的 priority_queue 想要构建小堆的时候,需要传递三个模板参数哒:存储 int 类型的小堆:priority_queue<int, vector<int>, greater<int>> heap。这个greater<int> 是个什么东西呢?

这个 greater<int> 就是一个模板类 priority_queue 能够实例化出来大堆和小堆的关键所在!

仿函数

看他的结构,greater<int> 应该也是一个模板类。在这个类的里面,重载了圆括号运算符,即:operator(),使得一个类的对象,能够像函数那样调用。这个东东就叫做仿函数。我们来看一个简单的例子吧:

struct GetLessNum
{
    int operator()(const int& a, const int& b)
    {
        return a < b ? a : b; 
    }
};

int main()
{
    GetLessNum getLessNum;
    int a = 10, b = 5;
    cout << getLessNum(a, b) << endl; //输出:5

    return 0;
}

在上面的例子中,我们定义了一个 GetLessNum 的类,类中重载了圆括号运算符。在 main 函数中,实例化出了一个对象,通过对象调用 operator() 从而通过类对象实现了函数调用的效果,这个就是仿函数啦!

揭秘 greater<int>

同样地,在 greater 这个模板类中,也重载了圆括号运算符。当我们构建大堆的时候,没有传入后两个模板参数,可见是给了缺省值。在 C 语言阶段,我们已经学习了堆,知道了构建大堆与小堆的区别:仅仅是在向上调整算法,和向下调整算法中的比较逻辑上不同,其余均是相同的。

C语言数据结构初阶(11)----堆_姬如祎的博客-CSDN博客

于是我们就可用通过模板参数来控制 priority_queue 中两个算法实现的比较逻辑!这样就能够实现根据传入模板参数的不同,构建出来不同的堆了!

namespace Tchey
{
    template<class T>
    struct less
    {
        bool operator()(const T& e1, const T& e2)
        {
            return e1 < e2;
        }
    };

    template<class T>
    struct greater
    {
        bool operator()(const T& e1, const T& e2)
        {
            return e1 > e2;
        }
    };
}

在使用 priority_queuepush 函数的时候,就会使用向上调整算法:对于大堆,如果子节点的值大于父节点的值,那么就需要交换两个节点的值,对于小堆;如果子节点的值小于父节点的值,那么就需要交换两个节点的值。

于是我们就可以通过传入的第三个模板参数来控制:如果传入 less<T>,刚好 less<T> 中的 opertor() 是小于的比较逻辑,就是小堆的向上调整算法;如果传入 greater<T>,刚好 greater<T> 中的 opertor() 是大于的比较逻辑,就是大堆的向上调整算法。

通过模板参数的控制,priority_queue 同时能够实现大堆和小堆,而不用单独写大堆和小堆。

priority_queue 的基本结构就可以这么写:

namespace Tchey
{
    template<class T>
    struct less
    {
        bool operator()(const T& e1, const T& e2)
        {
            return e1 < e2;
        }
    };

    template<class T>
    struct greater
    {
        bool operator()(const T& e1, const T& e2)
        {
            return e1 > e2;
        }
    };

    template<class T, class Container = vector<T>, class Cmp = Less<T>>
    class priority_queue
    {
    public:
    private:
        Container _con;
    };
}

priority_queue 的函数实现

向上调整算法

我们在向一个堆插入数据的时候就会用到向上调整算法:就拿大堆来说,你向大堆里面插入了一个数据,自然是要通过调整,使得插入的数据和原来的大堆重新形成一个新的大堆!

在这里插入图片描述

在这个插入的例子中,插入节点 9,不妨命名为 child。他的父节点 6,不妨命名为 parent。child 大于parent,交换两个节点的值。然后更新 child 和 parent,此时 child 依然大于 parent 交换两个节点的值。再次更新 child 和 parent。发现 parent < 0,结束循环。或者在比较的过程中 child < parent 也要结束循环。

这就是大堆的向上调整算法,至于小堆,比较逻辑反过来就行。

想要 priority_queue 根据传入的参数来决定是大堆的比较逻辑还是小堆的比较逻辑,这里就不能将比较逻辑写死,而是根据仿函数来比较!

void AdjustUp(int child)
{
    Cmp cmp; //根据第三个模板参数的类,实例化出来一个对象
    int parent = (child - 1) / 2; //根据child 找到 parent
    while(child)
    {
        if(cmp(_con[parent], _con[child])) //调用 operator() 来比较
        {
            swap(_con[parent], _con[child]);
            child = parent;
            parent = (child - 1) / 2;
        }
        else //不满足条件直接退出循环
            break;
    }
}

向下调整算法

向下调整算法在 pop 函数中使用哈!pop 的逻辑就是将堆顶的数据与堆中最后一个数据交换,然后对下标为 0 的元素来一次向下调整算法,就满足堆的要求啦!

在这里插入图片描述

我们来看上面的例子:这是一个大堆,删除堆顶的元素:我们先将堆顶的 7 和堆的最后一个数据 0 交换。然后对下标为 0 的元素,不妨命名为 parent。向下调整的逻辑是:选择 parent 的左右孩子中较大的那个孩子,不妨命名为 child,然后与 parent 进行比较,如果 child 大于 parent,那么交换 parent 和 child 两个节点。反之结束向下调整算法的逻辑。如果当 child 大于等于 vector 的 size 也要结束循环。

同向上调整算法,如果是小堆的话,只是比较逻辑不相同,其他的步骤均是一样的!因此向下调整算法 child 与 parent 的比较不能使用大于小于符号将比较逻辑写死。而是要使用仿函数来实现比较逻辑。

void AdjustDown(int parent)
{
    Cmp cmp; //实例化仿函数的对象
    int n = _con.size(); //vector 的大小
    int child = parent * 2; //通过 parent 找到 child

    while(child < n)
    {
        //选择左右孩子中较大的那个或者较小的那个,取决于第三个模板参数的传值
        if(child + 1 < n && cmp(_con[child], _con[child + 1]))
            child++;
        //如果满足条件,交换
        if(cmp(_con[parent], _con[child]))
        {
            swap(_con[parent], _con[child]);
            parent = child;
            child = parent * 2;
        }
        else //不满足直接退出
            break;
    }
}

void push(const T& val)

当你完成了向上调整算法与向下调整算法的书写,堆的接口实现就是信手拈来哈!push 函数是向堆中插入如一个元素,我们只需要在 vectorpush_back 一个元素,然后使用向上调整算法就可以啦!

void push(const T& val)
{
    _con.push_back(val);
    AdjustUp(_con.size() - 1);
}

void pop()

删除堆顶的元素只需要我们将 vector 中下标为 0 的元素与 vector 中的最后一个元素交换位置,然后调用 pop_back() 接口,最后对下标为 0 的元素使用一次向下调整算法就可以啦!


void pop()
{
    swap(_con[0], _con[_con.size() - 1]);
    _con.pop_back();
    AdjustDown(0);
}

bool empty()

我们只需要返回 vector 是否为空就行啦!

void empty()
{
    return _con.empty();
}

size_t size()

同样地,我们只需要返回 vector 的 size 就可以啦!


size_t size()
{
    return _con.size();
}

T& top()

返回堆顶的元素就是返回 vector 中下标为 0 的元素。


void top()
{
    assert(_con.size());
    return _con[0];
}

在这里插入图片描述

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

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

相关文章

040-第三代软件开发-全新波形抓取算法

第三代软件开发-全新波形抓取算法 文章目录 第三代软件开发-全新波形抓取算法项目介绍全新波形抓取算法代码小解 关键字&#xff1a; Qt、 Qml、 抓波、 截获、 波形 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项目结合了 QML&#xff08;Qt Meta-Object …

【错误: 找不到或无法加载主类】回归java运行的本质

【错误: 找不到或无法加载主类】回归java运行的本质 一&#xff0c;背景 当有了idea这种工具后&#xff0c;java的mian方法执行起来是如此简单&#xff0c;很少有人再手动编辑并通过命令行执行了。 同时&#xff0c;在当今Spring Boot盛行的今天&#xff0c;恐怕很少再有人执…

基于SSM的模具制造企业订单跟踪管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

阿里云国际服务器如何申请退款

如果您的服务器配置购买错了&#xff0c;可以通过工单方式申请退款如何发工单&#xff1f; 打开如下链接登录阿里云国际多云管理服务商_Cloud MSP_九河云 (9he.com) 选择一个类目&#xff0c;提交工单&#xff0c;编辑需求内容 退款之前一定记录好当前剩余余额&#xff0c;避免…

【LeetCode:150. 逆波兰表达式求值 | 栈】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【驱动开发】注册字符设备使用gpio设备树节点控制led三盏灯的亮灭

注册字符设备使用gpio设备树节点控制led三盏灯的亮灭 设备树&#xff1a; 头文件&#xff1a; #ifndef __HEAD_H__ #define __HEAD_H__ typedef struct {unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int IDR;unsigned int OD…

三.RocketMQ单机安装及集群搭建

RocketMQ单机安装及集群搭建 一&#xff1a;安装环境1.软硬件要求2.下载RocketMQ 二.安装单机MQ1.上传并解压2.目录介绍3.修改MQ启动时初始JVM内存4.启动NameServer与Broker5.测试RocketMQ 三.RocketMQ集群搭建1.集群概念特点2.集群模式分类3.集群工作流程4.双主双从集群搭建4.…

力扣刷题-队列-滑动窗口最大值

239. 滑动窗口最大值 给定一个数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回滑动窗口中的最大值。 进阶&#xff1a; 在线性时间复杂度内解决此题&#xff1f; …

【网络协议】聊聊http协议

当我们输入www.baidu.com的时候&#xff0c;其实是先将baidu.com的域名进行DNS解析&#xff0c;转换成对应的ip地址&#xff0c;然后开始进行基于TCP构建三次握手的连接&#xff0c;目前使用的是1.1 默认是开启了keep-Alive。可以在多次请求中进行连接复用。 HTTP 请求的构建…

【C++的OpenCV】第十四课-OpenCV基础强化(三):单通道Mat元素的访问之data和step属性

&#x1f389;&#x1f389;&#x1f389; 欢迎来到小白 p i a o 的学习空间&#xff01; \color{red}{欢迎来到小白piao的学习空间&#xff01;} 欢迎来到小白piao的学习空间&#xff01;&#x1f389;&#x1f389;&#x1f389; &#x1f496; C\Python所有的入门技术皆在 我…

JS--获取元素的高度与宽度

原文网址&#xff1a;JS--获取元素的高度与宽度_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍如何使用JavaScript获取HTML标签的高度与宽度。 读取的方法 document.getElementById("id").clientHeight 元素的尺寸属性 元素尺寸属性 说明 clientWidth 获取…

CentOS7非lvm给根分区扩容

首先查看现有磁盘信息和文件系统的信息 关闭虚拟机&#xff0c;右键虚拟机&#xff0c;点击设置&#xff0c;选中硬盘&#xff0c;右边点击拓展&#xff0c;然后给磁盘空间增加到指定的大小 打开虚拟机&#xff0c;查看扩容后的分区大小&#xff0c;此时会发现根分区大小并…

threejs(10)-WEBGL与GPU渲染原理(难点)后期再消化亦可

一、渲染管线 WebGL 是什么 WebGL (Web图形库)是一个JavaScript API,可在任何兼容的Web浏览器中渲染高性能的交互式3D和2D图形,而无需使用插件。WebGL通过引入一个与OpenGL ES 2.0非常一致的API来做到这一点,该API可以在HTML5 元素中使用。这种一致性使API可以利用用户设备提…

Airtest 如何测试手机 APP?

引言 Airtest 是网易出品的一款基于图像识别的自动化测试工具&#xff0c;主要应用在手机 APP 和游戏的测试。一旦使用了这个工具进行 APP 的自动化&#xff0c;你就会发现自动化测试原来是如此简单&#xff01;&#xff01; 如果对软件测试、接口、自动化、性能测试、测试开…

Git 入门指南:从新手到高手的完全指南

Git是一种强大的分布式版本控制系统&#xff0c;广泛应用于软件开发中。它的使用不仅可以帮助开发团队更好地管理代码&#xff0c;还可以提高团队协作效率和代码质量。随着软件开发的不断发展&#xff0c;版本控制成为了程序员必备的一项技能。 Git的基本概念 Git的基本概念对…

阿里云服务器双11特惠99元一年云服务器ECS经济型e实例

2023阿里云服务器双11优惠价格99元一年经济型e实例&#xff0c;并且续费不涨价&#xff0c;云服务器ECS-经济型e实例2核2G配置、3M带宽、40G ESSD entry系统盘优惠价99元一年&#xff0c;原价956.64元/年&#xff0c;可用于中小型网站建设、开发测试、小程序或app搭建&#xff…

十九、类型信息(4)

本章概要 注册工厂类的等价比较反射&#xff1a;运行时类信息 类方法提取器 注册工厂 从 Pet 层次结构生成对象的问题是&#xff0c;每当向层次结构中添加一种新类型的 Pet 时&#xff0c;必须记住将其添加到 LiteralPetCreator.java 的条目中。在一个定期添加更多类的系统…

锁表后引发的几种删除方式与不同的扩展

在开发过程可能会遇到一些特殊场景&#xff0c;诸如我想删除某表&#xff0c;但是无法删除&#xff0c;去找原因发现是发生了锁表&#xff0c; 锁表指的是在执行一个事务时&#xff0c;该事务获取了一个锁并保持其锁定状态&#xff0c;直到事务完成或手动释放锁&#xff0c;导…

【零参考GAN:Pansharpening】

ZeRGAN: Zero-Reference GAN for Fusion of Multispectral and Panchromatic Images &#xff08;用于多光谱和全色图像融合的零参考GAN&#xff09; 本文提出了一种融合低空间分辨率多光谱(LR MS)和高空间分辨率全色(PAN)图像的新的全色锐化方法–零参考生成对抗网络(ZeRGAN…

深度学习之基于ResNet18的神经网络水果分类系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介二、功能三、神经网络水果分类系统四. 总结 一项目简介 基于ResNet18神经网络的水果分类系统是一个利用深度学习技术进行水果图像分类的系统。下面是该系统…