C++入门第九篇---Stack和Queue模拟实现,优先级队列
前言:
我们已经掌握了string vector list三种最基本的数据容器模板,而对于数据结构的内容来说,其余的数据结构容器基本都是这三种容器的延申和扩展,在他们的基础上扩展出更多功能和用法,今天我们便来模拟实现一下C++库中的栈和队列以及优先级队列。
1.适配器:
让我们打开stack模板库的介绍界面,我们会看到这样一个东西:
stack的模板中的这个Container是什么呢?没错,在这里我们将其称之为适配器,它的作用是,为我们的的栈或者其他数据结构自动匹配底层的容器模板库,如下:
template<class T, class Container = deque<T>>
class stack
{
private:Container _con;
};
int main()
{hbw::stack<int,list<int>> a1;
}
你会发现,我们的私有的成员直接就是适配器类型的,这样当我们的主程序让其适配器为list类型的时候,适配器会去判断当前的stack的功能函数能否复用到list的成员函数中,倘若可以,我们就可以通过list的底层模板函数功能直接实现出stack的功能。
因此,只要我们的容器的底层容器的功能能够匹配我们当前容器的功能,适配器就可以自动适配到对应的容器中,这样的复用大大节省了效率,让我们在开发时不用再去自己构建复杂的函数和对应的容器即可实现,或许我这样说,你还没法理解,我们接下来的栈和队列的模拟实现的代码,你通过去与我C语言实现的栈和队列去对比即可明白。
deque容器:
依旧是拿出来我们的stack的模板参数,你会发现,它在适配器参数上给了个缺省值deque,根据刚才的是适配器的知识点我们知道,这里的container指代的是一个容器类型,故我们的deque也一定是一个容器类型,因此,我们可以先猜测一下,这个deque为何让它作为缺省参数呢?
根据之前的C语言栈的队列篇我曾经说到,栈和队列都是可以同时使用数组和链表来实现的,对应到C++里,也就是说vector或者list都可以作为stack的底层,因此,作为缺省值,这个deque理论上应当具备两者都有的功能,如下:
没错,从它的成员函数来看,它既可以和vector一样去利用[]访问下标,也可以实现list的头删头插,访问头尾的功能,因此,在这里让deque作为缺省值,确实是合适不过的,但是,它是如何同时具备两种数据结构的特点的呢?下面让我们一起来分析一下它的容器构建原理:
deque容器的介绍:
deque被称为双端队列。虽然叫它队列,但实际上它并不是队列,也就是说它不是仅仅可以尾插头删,只不过叫这个名字,这个首先要明确,别搞混。它是一种从中间向两边延申的结构,在它的模板中,我们看到了deque使用了内存池allocator来存储数据:
或许说到这里,我们大致猜出来deque的大体模型是怎样的了,在我看来更类似即可几个数组通过某种方式拼接,让这些数组在逻辑上是连续的,deque的具体构造如下图:
我们的deque支持两个容器的功能,可以说它的功能是全面的,而我们也知道它使用了内存池allocator,这个内存池的就是我们上图中的BUFF子数组,每一个BUFF数组的长度都是统一的,这样方便我们的下标访问执行。
它实现功能大致如下:
1.尾插:
则在最后一个数组之后再开辟一个新的BUFF,将尾插数据放在这个数组的第一位
2.头插:
在第一个BUFF之前再开辟一个BUFF,将头插数据放在这个数组的最后一位
我们发现,这样的处理方式是没有扩容的消耗的,也不需要挪动数据,很高效
3.之间插入:
中间插入时,我们只有两种办法:
1.BUFF进行扩容/控制数据个数
2.局部整体挪动
这两种方法都可以,但是都有各自的缺点,比如,如果我们对BUFF进行扩容,就会影响我们的[]下标访问,根据deque的结构我们可以总结出,deque下标的计算公式是:x=i/10+i%10,当然,这里是我们假设我们的BUFF都为10的情况下,因此,首先/10确定对应的元素在哪个BUFF子数列里,然后%10确定它在这个子数列的第几个,这样,我们就可以精确的锁定位置,从而让下标访问生效。所以,一旦我们去修改BUFF的长度,就会导致我们这个公式直接无效,十分影响[]访问,但倘若挪动数据,则又会消耗大量的时间,因此,两种方案各有取舍,我们可以根据库STL去看看官方是如何实现的。
我们的deque只涉及到中控数组的扩容问题。但是,由于存储的数据是指针,故只要进行浅拷贝即可,因此,它扩容的消耗并不大。
综上,虽然deque兼具vector和list的双重特点,但它却没有将自己的特性优化到极致,我们可以说deque在头插和尾插方面确实有很大的优势,但是它的下标访问和中间的增删都不如vector和list,具体的deque的实现如下:
它一共有四个指针去控制整个结构,其中cur用来指针的实时位置,first指向一个BUFF的头,last指向这个BUFF的尾部,而node则用来控制中控数组的指针,当cur==last的时候,node自动向下移动一位,从而实现了下标的连续访问。
2.stack模拟实现:
有了前面的知识铺垫,我们已经不需要怎样去解释实现的细节,直接按照代码理解即可
namespace hbw
{template<class T, class Container = deque<T>>//这里通过这个模板参数控制底层的容器是哪个类型,是谁,这个Container即为适配器,不管底层容器是谁,它都可以适配一个后进先出的栈,如果适配器不适用(比如对应的底层的容器不支持上层的功能),则编译器会报错class stack//我们在这里加上了一个缺省参数,deque容器,这就符合了我们倘若不传对应的数据类型,编译器就会为我们自动适配deque容器,deque容器是一个功能很全面的容器,虽然效率上不够极致,但是泛用性强,故用在这里可以支持list和vector两者的全部功能,从而有了作为缺省值的条件{public:void push(const T& x){_con.push_back(x);}void pop(){_con.pop_back();}const T& top(){return _con.back();}bool empty(){return _con.empty();}size_t size(){return _con.size();}private:Container _con;};
}
3.queue模拟实现:
namespace hbw
{template<class T, class Container = deque<T>>class queue{public:void push(const T& x){_con.push_back(x);}void pop(){_con.pop_front();}const T& front(){return _con.front();}const T& back(){return _con.back();}bool empty(){return _con.empty();}size_t size(){return _con.size();}private:Container _con;};
}
他们两个的本质都是复用,复用底层的函数封装成自己的函数功能,这就是适配器的优点所在,不过,值得注意的是,适配器只能转换为符合当前功能的底层容器,对于不符合功能的容器,编译器是没法通过的
4.优先级队列priority_queue容器:
何为优先级队列,即是一个优先按照升序或者降序存储输出值的容器,我们可以先看一下它的一些模板功能:
没错,看到它的函数功能,它可以返回顶部的元素,又结合它按照顺序输出数的特性,我们不难看出,它很像我们曾经模拟实现的一个数据结构-堆。因此,它的默认底层容器为vector,也就死用数组作为默认的缺省底层容器
没错,虽然叫它优先级队列,但实际上,它的本质就是一个升序或者降序的堆,既然是堆,我们就可以复用我们之前学过的堆的各种接口,故它的模拟实现如下:
首先,我们依旧使用适配器来作为priority_ queue的底层如下:
template<class T,class Container=vector<T>,class Comapre=Less<T>>private:Container _con;};
1.push函数:
void Adjustup(int child)//向上调整{Compare com;int parent = (child-1)/ 2;while (child > 0){if (com(_con[parent],_con[child])){std::swap(_con[child], _con[parent]);child = parent;parent = (parent - 1) / 2;}else{break;}}}
void push(const T& x)
{_con.push_back(x);Adjustup(_con.size()-1);
}
push函数,我们的基本思路就是,将任意数据放入我们的底层容器后,将其向上调整到对应的位置,保证我们的堆的数据之间的关系不会乱,这里的向上调整的函数之前实现过,在这里我们可以复习一遍。
2.pop函数:
void Adjustdown(int parent)//向下调整
{Compare com;int child = 2 * parent + 1;while(child<_con.size()){if (child + 1 < _con.size() &&com(_con[child],_con[child+1])){child++;}if(com(_con[parent],_con[child])){std::swap(_con[child], _con[parent]);parent = child;child = 2 * child + 1;}else{break;}}
}void pop(){std::swap(_con[0],_con[_con.size()-1]);_con.pop_back();//尾删一个Adjustdown(0);}
对于删除来说,我们一定是删除头数据比较有价值,故我们采取的方式是,首先让头尾数据交换位置,然后将尾部数据删除,然后将头数据向下调整到正确的位置,保证堆的数据大小关系不会错误。
3.其余的接口
const T& top()
{return _con[0];
}bool empty()
{return _con.empty();
}size_t size()
{return _con.size();
}T& operator[](size_t i)
{return _con[i];
}
都是一些很简单的接口,我在这里不多解释,读代码就应该能看懂。
4.仿函数(关键!!!!):
在priority_queue中,我们看到了这样的一个模板参数,compare,它的默认参数值给了一个less,经过尝试,我们知道,这个less实际上就是构建大堆的意思,但是,在这里的这个Compare是什么意思呢?这就是我们要说的仿函数.
那什么是仿函数呢?我们先看一个例子:
template<class T>
class Less//仿函数less
{
public:bool operator()(T& x, T& y){return x < y;}
};template<class T>
class Greater//仿函数greater
{
public:bool operator()(T& x, T& y){return x > y;}
};
仿函数的本质就是一个只封装了一个()运算符重载的函数的类,当我们使用的时候,直接在类名后面带上括号即可调用这个函数,导致我们看到它的形式就类似一个函数调用,但本质上它依旧是一个类,所以称它为仿函数。如下:
void Adjustdown(int parent)//向下调整
{Compare com;int child = 2 * parent + 1;while(child<_con.size()){if (child + 1 < _con.size() &&com(_con[child],_con[child+1])){child++;}if(com(_con[parent],_con[child])){std::swap(_con[child], _con[parent]);parent = child;child = 2 * child + 1;}else{break;}}
}
当我们看到这个compare com时,这个com就是仿函数类,后续比较的时候,直接利用仿函数传入参数就可以直接进行比较,比如这里的com(_con[child],_con[child+1]。
仿函数的优点和用处:
有了仿函数,我们就可以像预处理那样对一些运算方法进行复用和小成本的修改,比如我们想从建立大堆变成建立小堆,就像上面一样,分别写一个大堆一个小堆两个仿函数,想使用哪个直接在模板参数里实例化即可,这样提高了效率。
但是仿函数的用处更多的在于它替换了函数指针,我们不用在写繁杂的函数指针参数去使用回调函数,不仅难写而且易错,而是利用仿函数,调用即可,其实本质上仿函数和回调函数的用处一样的,但是仿函数更加好用和简单。
一般仿函数都写成模板类,让其可以针对任意类型进行函数使用,让其运用的场景更加广泛。
5.构造函数:迭代器数据传入构造建堆
priority_queue()//写一个默认无参的构造函数,让编译器自己生成默认的构造函数构成重载
{}template<class InputIterator>
priority_queue(InputIterator first, InputIterator end)//利用区间进行构造函数:_con(first,end)//首先利用vector可以区间构造的特点,先把数据放入到vector容器中
{int i = 0;for (i = (_con.size() - 2) / 2; i >= 0; i--){Adjustdown(i);}
}
priority_queue支持传入迭代器区间去建堆,其构造的特点就是首先利用底层容器的vector支持迭代器区间构建数组的特点先初始化_con,然后利用向下建堆的方法,从而建立一个堆,但是写下这个构造函数之后,我们的默认构造函数就没有了,也就是说,后续的构造函数都得传区间,这个是不一定,故我们再写一个默认无参或者全缺省的构造函数,这个就是由编译器自动生成的构造函数,由此,我们就可以同时支持区间迭代器构造和默认构造了。
总结:
以上便是我们的stack queue 优先级队列的基本内容,到这里,我们基本已经掌握了STL库的基本容器模板,但我还是要强调的一点,我已经反复强调了,模拟实现模板的目的是让我们更好的去使用模板,从C语言的思维中走出来,尝试利用C++的思维去解题和分析,熟练的利用模板去简化代码和提高开发效率。同时,模拟实现的过程中我们也学到了如迭代器,迭代器自定义封装,内存池,如何忽略空格任意字符识别,如何扩容,迭代器失效,仿函数等一系列更加重要的属于C++的知识点,我认为这些才是关键,因此,我们应该要抓住我们的重点去学习,在我看来,这是最关键的。
相关文章:

C++入门第九篇---Stack和Queue模拟实现,优先级队列
前言: 我们已经掌握了string vector list三种最基本的数据容器模板,而对于数据结构的内容来说,其余的数据结构容器基本都是这三种容器的延申和扩展,在他们的基础上扩展出更多功能和用法,今天我们便来模拟实现一下C库中…...

计算机组成原理(计算机系统概述)
目录 一. 计算机的发展二. 计算机硬件的基本组成2.1 早期冯诺依曼机2.2 现代计算机的结构 三. 各硬件的工作原理3.1 主存储器的基本组成3.2 运算器的基本组成3.3 控制器的基本组成 四. 计算机的工作过程 \quad 一. 计算机的发展 计算机系统 硬件 软件 #mermaid-svg-gp2AsYELE…...

Qt手写ListView
创建视图: QHBoxLayout* pHLay new QHBoxLayout(this);m_pLeftTree new QTreeView(this);m_pLeftTree->setEditTriggers(QAbstractItemView::NoEditTriggers); //设置不可编辑m_pLeftTree->setFixedWidth(300);创建模型和模型项: m_pLeftTree…...

【开源】基于Vue.js的城市桥梁道路管理系统的设计和实现
项目编号: S 025 ,文末获取源码。 \color{red}{项目编号:S025,文末获取源码。} 项目编号:S025,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询城市桥…...

2、git进阶操作
2、git进阶操作 2.1.1 分支的创建 命令参数含义git branch (git checkout -b)<new_branch> <old_branch>表示创建分支-d <-D>删除分支 –d如果分支没有合并,git会提醒,-D强制删除-a -v查看分支-m重新命名分支commit id从指定的commi…...

集线器-交换机-路由器
1.集线器(Hub) 集线器就是将网线集中到一起的机器,也就是多台主机和设备的连接器。集线器的主要功能是对接收到的信号进行同步整形放大,以扩大网络的传输距离,是中继器的一种形式,区别在于集线器能够提供多端口服务,也…...

金融众筹模式系统源码 适合创业孵化机构+天使投资机构+投资基金会等 附带完整的搭建教程
随着互联网技术的发展和金融市场的开放,金融众筹模式逐渐成为一种新型的融资方式。这种模式通过互联网平台聚集大量投资者,共同参与到一个项目中,为项目提供资金支持,最终获得投资回报。今天罗峰给大家分享一款金融众筹模式系统源…...
大数据学习(24)-spark on hive和hive on spark的区别
&&大数据学习&& 🔥系列专栏: 👑哲学语录: 承认自己的无知,乃是开启智慧的大门 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言📝支持一下博主哦ᾑ…...

SQLite3 数据库学习(六):Qt 嵌入式 Web 服务器详解
参考引用 SQLite 权威指南(第二版)SQLite3 入门 1. Apache 搭建 cgi 环境 1.1 什么是 Apache Apache 是世界使用排名第一的 Web 服务器软件 它可以运行在几乎所有广泛使用的计算机平台上,由于其跨平台和安全性被广泛使用 1.2 具体搭建流程…...

各平台chatGPT使用体验(国内外)
首推:openAI 地址:https://chat.openai.com/ 这个真的很好用,而且回复的结果也基本让让人满意,个人首推,而且对比国内的除了回答更令人满意外,它更连贯,不像国内的gpt一句一问,跟进…...

机器学习【02】在 Pycharm 里使用 Jupyter Notebook
只有 Pycharm 的 Professional 版才支持 Jupyter Notebook 一.新建一个项目 参考新建项目 二.相关设置 右键你的项目名,新建一个JupyterNotebook文件 新建后发现 点击最右边的install jupyter可以自动安装 也可以使用命令行在对应的虚拟环境中安装 我们使用直…...
什么是proxy代理?
1. 什么是proxy代理 代理(Proxy)是 JavaScript 中一种非常强大而灵活的功能。代理允许你拦截并覆盖对象的默认行为,提供了一种拦截、定制和扩展对象操作的机制。 简单说,就是在访问对象属性或者赋值时,可以做一些额外…...
opencv-python读取的图像分辨率太大不能完全显示
如果使用OpenCV-Python读取的图像分辨率太大,无法完全显示在屏幕上,可以考虑以下几种方法: 1.缩放图像:使用OpenCV的resize函数,将图像缩小到适合屏幕显示的大小。例如,可以将图像的宽度和高度都缩小到屏幕…...

【ArcGIS Pro微课1000例】0038:基于ArcGIS Pro的人口密度分析与制图
文章目录 一、人口密度二、人口密度分析1. 点密度分析2. 核密度分析三、结果比对一、人口密度 人口密度是指单位土地面积上居住的人口数,通常以每平方千米或每公顷内的常住人口为单位计算。人口密度同资源、经济密切结合,因此,科学准确地分析人口密度的分布情况,对合理制定…...

Python 安装Vue依赖包发生异常:npm ERR! notsup Required: {“node“:“^18.17.0 || >=20.5.0“}
异常: 原因:node和npm要求升级为高版本 解决:重新安装node环境 (1) 官网下载Node.js (2)双击安装node.js (3)运行查看...
TypeScript 项目 Airbnb 语法风格 ESLint 配置
TypeScript 项目 Airbnb 语法风格 ESLint 配置 1. 配置 安装: npm i -D eslint-config-airbnb-typescript typescript-eslint/eslint-plugin^6.0.0 typescript-eslint/parser^6.0.0配置: .eslintrc.js: module.exports {root: true,env: {node: true…...
怎么使用sentinel,以及所有的知识点
Sentinel是一个开源的流量控制和实时监控系统,主要用于保护企业级应用程序免受不良的请求。下面是使用Sentinel需要了解的知识点: 1. 什么是流量控制? 流量控制指的是限制应用程序的请求流量,防止过多的请求超出系统的承受范围。…...

中国一年有457万人确诊癌症!医生提示:这4种食物,再爱吃也要管住嘴
癌症是威胁人类生命健康的重大疾病,癌症的发生因素一直以来都是专家学者重点探索的课题。据世卫组织最新公布的数据显示,食物或与癌症发生之间存在着密切的联系,某些食物的摄入过多可能会增加患癌症的风险,所以我们应该警惕&#…...

小程序项目:springboot+vue基本微信小程序的宠物领养系统
项目介绍 当今科技发展迅速,交通环境也变得越来越复杂。人们的出行方式变得多元化,这给视障人士带来了一定的困扰。而导盲犬可以帮助视障人士外出行走,提高他们的生活质量。在我国,导盲犬的数量远远少于视障人士的数量。由于导盲…...

数据挖掘 K近邻
什么时候用K近邻? 交叉验证的时候。最常见的交叉验证方法是K折交叉验证,其中数据集被均匀分成K个子集,称为折,然后执行K次训练和测试,每次选择不同的折作为测试集,其余的作为训练集。最后,将K次…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...

零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程
STM32F1 本教程使用零知标准板(STM32F103RBT6)通过I2C驱动ICM20948九轴传感器,实现姿态解算,并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化,适合嵌入式及物联网开发者。在基础驱动上新增…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...
Vue 3 + WebSocket 实战:公司通知实时推送功能详解
📢 Vue 3 WebSocket 实战:公司通知实时推送功能详解 📌 收藏 点赞 关注,项目中要用到推送功能时就不怕找不到了! 实时通知是企业系统中常见的功能,比如:管理员发布通知后,所有用户…...

2025-05-08-deepseek本地化部署
title: 2025-05-08-deepseek 本地化部署 tags: 深度学习 程序开发 2025-05-08-deepseek 本地化部署 参考博客 本地部署 DeepSeek:小白也能轻松搞定! 如何给本地部署的 DeepSeek 投喂数据,让他更懂你 [实验目的]:理解系统架构与原…...
python打卡第47天
昨天代码中注意力热图的部分顺移至今天 知识点回顾: 热力图 作业:对比不同卷积层热图可视化的结果 def visualize_attention_map(model, test_loader, device, class_names, num_samples3):"""可视化模型的注意力热力图,展示模…...