STL_stack和queue(deque priority_queue)

article/2025/8/6 3:27:25

前言

本文主要介绍,本人的学习心得和知识汇总,本篇博文对于STL知识的讲解侧重于难点,不会每一个都细细讲解。本文主要对适配器设计模式展开讲解,对反向迭代器和优先级队列重点讲解。STL对栈和队列的设计不同于之前c语言设计的栈和队列,不再是手搓结构,我们STL借用别人的容器实现,这就是适配器,要注意适配器是一种设计模式,是一种全新的东西,这也是我们要学习他的原因。

正文

适配器简介

适配器是一种设计模式,就是复用别人的容器,STL里的stack没有自己在实现一个顺序表,而是增加一个类模板,我们一般称这个类模板为Container,stack里的函数再去调用这个类模板的函数。假如你传入了一个vector容器,那么stack里的push既可以去调用vector的push_back。这种设计思想,也叫设计模式,我们称为适配器。大家先这么简单理解,在下面会逐渐地加深理解适配器。

实现适配器的stack和queue (STL)

首先STL的stack和queue和数据结构使用的接口和方法没有什么区别,不过有一个缺点就是STL里的栈和队列里的pop接口是没有返回值,这有点不利于使用,很多时候我们是需要再pop之后接受这个被pop的值的。但是STL却没有实现,其他语言,很多都实现了,隔壁的java就很优秀,实现了返回值。

首先看看栈的代码

namespace star
{template<class T,class Container = deque<T>>class stack{public:void push(const T& x){_con.push_back(x);}void pop(){_con.pop_back();}T& top(){return _con.back();}size_t size(){return _con.size();}bool empty(){return _con.empty();}private://自定义类型Container _con;};
}

是不是很简单,一目了然,这就是适配器的好处,增加了灵活性的同时还简化的代码的编写

这里就是Container就是我们说的外部容器,外部不提供,我们使用默认的vector<T>

template<class T,class Container = vector<T>>

我们再来看一下接口

        void push(const T& x){_con.push_back(x);}void pop(){_con.pop_back();}T& top(){return _con.back();}size_t size(){return _con.size();}bool empty(){return _con.empty();

这些接口,内部就是去调用容器的接口,就像套娃一样,不像c语言,需要自己去实现底层代码,这里增加了复用性,避免了大量的重复性代码块。拿push举例,内部就是去调用容器的push_back()。这样的设计好处就是,我们就是套了个模板,使用者想使用那个容器就上传那个容器,同样使用者也要知道那个容器适合栈结构,这些都由使用者去负责。灵活性和实用性非常高。

我们再来看看队列的代码

namespace star
{template<class T, class Container = deque<T>>class queue{public:void push(const T& x){_con.push_back(x);}void pop(){//_con.erase(_con.begin());_con.pop_front();}T& front(){return _con.front();}T& back(){return _con.back();}size_t size(){return _con.size();}bool empty(){return _con.empty();}private://自定义类型Container _con;};
}

是不是发现和上面的栈差不多,一样就是要个模板,里面复用容器的接口。这里多了一个back()接口,这是STL的一个特别之处,这是因为在实际中,取尾的需求比较频繁,所以,STL就是实现了这个接口。

看到这里那你会发现,很简单是不是,这栈和队列不是so easy吗?c语言干嘛设计这么复杂?其实c语言和c++相比就是,c语言是底层语言,手动挡用的语言。c++就是自动挡的语言,不需要你了解离合与刹车之间的关系,就可以把车开走。但是,你如果不学c语言,底层逻辑。你会发现你的c++代码出了问题,你只能问al,不能自己解决。如果你是这样的一个程序员,那你觉得雇佣你和雇佣al有啥区别?

好了,回到正题,上难度。上面两段代码都有个deque,这是个啥?这其实是一个双端队列?这里为啥要用这个队列呢?stack用vector,queue用list不香吗?我们现在就来学习学习这个deque,看下STL为啥要用这个容器?

来做下题目吧

150. 逆波兰表达式求值 - 力扣(LeetCode)

这道题目,主要是思想上想不到,这里主要运用stack地先进后出地思想,有需要地友友们可以做一下。

deque

首先看看在这个容器的接口

你会发现这个接口既有头插又有尾插,很全面。STL里的vector头插效率很低,直接不提供头插接口,而list呢,尾插效率又低。除非你设计成带头双向链表,这样就很能打了,但是还是避免不了他的空间开销大和随机访问的效率低。那我们能不能实现一个容器,既有vector的优点和list的优点呢?答案就是deque,他就是一个御萝双修的一个产品,但他真的是一个六边形战士吗?我们来讲解下的它实现方式,你就会知道啦。

它的设计是有一个中控器,就是一个指针数组,每个指针指向一段buff数组。他的buff数组大小是固定的,相比vector扩容,他的拷贝数量极少,他拷贝的是指针而已,而不是每个数据,这样效率是极高的,缓解了扩容问题。他的中控器是默认在中间插入,所以他的头插就很方便了,只用看最前面的buff数组是不是满的就行,尾插也是同理。他的这样结构决定了他的随机访问就没有vector极致,他看似连续,实际上不是连续,需要你先算在那个buff,再算在buff的具体位置。可是相比vector,我们就是一个解引用,计算都不用计算。尤其当数据大量的随机访问的时候,他的效率是比较低的相比vector。还有他的中间插入和删除也是一大缺点,首先你就要计算buff位置,其次就是扩容,这里扩容可不能直接增加buff的容量,这样子随机访问的效率会更低,库里也没有这样实现,他的大方向是再开辟一块buff实现。其实你想想,他的中间操作时很复杂的,这里涉及一个中控器,还要控制这个buff数组,首位还好,可是中间的操作,这里变更位置,还要结合迭代器走,他的迭代器是一个自定义类型,里面有四个变量,分别是cur,first,last,node,四个指针,维护遍历。虽然他相比list高速缓存利用率高,可是,中间的插入和删除比list差远了。我们这里简单了解一下就行,这个容器底层实现是很复杂的。这个容器,适合首位的大量插入和删除,但是不适合中间的大量插入和删除。但是它刚刚好适合stack和queue这两个结构。push和pop很适合deque的头尾插入删除的高效率。因此,STL里把deque当作栈和队列的默认容器。其实你会发现,deque的综合性是还可以的,只不过在极致方面上和vector,list差的有点多。

反向迭代器的涉及模式

反向迭代器很好运用了适配器的这种涉及模式,但是相比栈和队列,这里就有点复杂了。因为,这里的反向迭代器,是适用于任何的容器的。

他的底层就是包装了一个正向迭代器,调用正向迭代器的接口实现反向迭代器的功能。我们这里反向迭代器是根据正向迭代器来设计,也就是说,两者的接口都一样。

先给大家看看代码

namespace che
{// 适配器 -- 复用template<class Iterator, class Ref, class Ptr>struct Reverse_iterator{Iterator _it;typedef Reverse_iterator<Iterator, Ref, Ptr> self;Reverse_iterator(Iterator it):_it(it){}//错位设计Ref operator*(){Iterator tmp = _it;return *(--tmp);}//这里复用this指针的operator* 完成错位Ptr operator->(){return &(operator*());}//返回迭代器本身 这里是个适配器self& operator++(){--_it;return *this;}self operator++(int){self tmp(_it);--_it;return tmp;}self& operator--(){++_it;return *this;}self operator--(int){self tmp(_it);++_it;return tmp;}bool operator!=(const self& s) const{return _it != s._it;}bool operator==(const self& s) const{return _it == s._it;}};
}

看一下这里为啥要--
 

	Ref operator*(){Iterator tmp = _it;return *(--tmp);}

看一下这里分析图

这里是对称设计,所以,你再返回的时候需要--获得有效数据,但是这个接口是返回当前迭代器的值,你如果对this指针直接操作,再次访问的时候,值就会改变,这不符合我这个接口的功能需求,所以需要构建一个临时变量,对临时变量操作,这样就不会影响了。

同理这里返回指针也是上面这个原因,这里我们就复用上面的接口就行

        Ptr operator->(){return &(operator*());}

这里需要注意一点,你的反向迭代器是个适配器,不是具体的指针,大家千万不要拿着vector迭代器的思想去往这里套,会绕进去的。这里实际上是一个模板,我们只需要拿一个正向迭代器去往里上传,这个正向迭代器可能是类或者就是一个指针。我们可以只需要提取这些正向迭代器的共性,就是都提供了++,--等重载函数,我们就需要使用这些重载函数完成反向迭代的功能。这里我们在理解底层的时候你不能把它简单理解成一个指针,要把它当成一个模板理解。在使用的时候,你可以把它当成一个指针来理解,因为我们的迭代器设计之初就是效仿指针来是实现的。他的使用语法和指针是一样的,所以,你在使用的时候,把它当成指针来使用完全没理解。在理解底层的时候,你可以一切把他的功能设计是为了实现指针的语法一样去理解他的设计。

下面地赋值重载是重载函数,子打错了


这里的适配器设计模式,很像我们的共厂,只生产标准件。这里的反迭代器就很好的体现了,我不管你的正向迭代器底层咋实现,你只要给我提供这些赋值重载,提供这些接口,让我可以使用就行,管你其他的呢?标准就是这些接口。

优先级队列

优先级队列和我们之前学的队列,实际上底层是翻天覆地的变化。这里的队列更多的是体现的了队列的思想吧。我们暂时把之前数据结构的队列称作普通队列。普通队列满足先进先出,但是优先级队列不满足先进先出,这就是它优先级的体现。他的的数据是按照一定顺序的,比如降升序。但他之所以能叫做队列是因为他和队列相似,都是固定的一端进数据和出数据,就是逻辑结构相似而已。甚至你可以认为他就不是个队列。优先级队列的设计也是采取适配器的设计模式,并且还加入了仿函数。

什么是仿函数

一个类可以向函数一样使用,我们称这种类叫做仿函数,所以仿函数的本质是一个类,一般每部会实现重载函数,从而达到函数的功能。

先看看代码

#pragma once
#include <vector>//优先级队列 不是一个队列 
//底层是一个堆(大小堆)默认是大堆
//这里很奇怪 less<T> 是大堆 greater<T> 是小堆 是反着来的 你可以认为堆就是天生反骨namespace star
{//记得创建Compare对象 你使用的是他的重载方法 这里没有静态 这里不是Java 学java学傻了template<class T,class Container = vector<T>,class Compare = Less<T>>class priority_queue{private://一般建堆的时候用 popvoid AdjustDown(int parent){Compare com;//先左孩子int child = parent * 2 + 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 = parent * 2 + 1;}else{break;}}}//插入数据时用void AdjustUp(int child){Compare com;int parent = (child - 1) / 2;//当child为0的时候就不需要调整了 parent都变成负数了 还向上调整啥while (child > 0){//判断大小if (com(_con[parent], _con[child])){//交换std::swap(_con[child], _con[parent]);//更新child = parent;parent = (child - 1) / 2;}//不需要直接breakelse{break;}}}public:priority_queue(){}template<class InputIterator>priority_queue(InputIterator first, InputIterator last){//插入数据while (first != last){_con.push_back(*first);++first;}//调整数据//向下调整的前提是左右孩子都是一个堆(大堆/小堆)//这里过程类似于递归 for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--){AdjustDown(i);}}//void pop(){std::swap(_con[0], _con[_con.size() - 1]);_con.pop_back();AdjustDown(0);}void push(const T& val){//_con.push_back(val);AdjustUp(_con.size() - 1);}const T& top(){return _con[0];}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};//仿函数/函数对象//仿函数 一个类可以像函数一样使用 替代c语言的函数指针 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;}};
}

很惊讶吧,他竟然有向上调整和向下调整,没错他的底层就是个堆。因此它的容器采取的是vector,而不是list。因为它需要大量的随机访问,并且数组实现结构简单,要知道一个链表实现堆是很复杂的。

他这里的逻辑是,在你实例化对象时,上传一个仿函数,指定优先级,默认是less,也就是大根堆,他这里是反逻辑的,之所以为啥不用greater代表大根堆,我也很疑惑。所以大家记得时候就想,设计堆和二叉数的设计都很反逻辑。那是怎么实现根据你的仿函数而改变大小堆呢?

看一下这里

	void AdjustUp(int child){Compare com;int parent = (child - 1) / 2;//当child为0的时候就不需要调整了 parent都变成负数了 还向上调整啥while (child > 0){//判断大小if (com(_con[parent], _con[child])){//交换std::swap(_con[child], _con[parent]);//更新child = parent;parent = (child - 1) / 2;}//不需要直接breakelse{break;}}}

他这里是直接实例化你的,仿函数的对象,使用对象去比较。这样子我们的代码就不是写死的。而是有点动态的意思,有较高的灵活性。不像以前写的c语言代码,都是死的,不灵活。在这里,你想大堆就大堆,想小堆就小堆,只要传入一个仿函数。

小题目一道

215. 数组中的第K个最大元素 - 力扣(LeetCode)

这里主要运用了大小堆地性质,就是对于排序时的思想,这道题大小堆都可以实现,有需要地友友们也可以做一下。

总结

本篇博客是围绕这适配器展开讲解,大体介绍STL里的栈和队列还有优先级队列,对于双端队列我建议了解即可,学习他其实没什么必要。要是他真的很厉害,我们就不用学vector和list了。这里在学习底层代码地时候,我建议边画图边做,出错了,再调试查看,不建议直接源码,因为一般情况源码也不太好看懂,库里封装地更厉害。


http://www.hkcw.cn/article/whvdRsDpmr.shtml

相关文章

从印巴空战看数据制胜密码:元数据如何赋能数字战场

2025年5月的印巴空战震惊世界&#xff1a;巴基斯坦以6:0的压倒性战绩击落印度“阵风”等战机&#xff0c;这场胜利的背后不仅是武器代差&#xff0c;更是“数据链体系”的降维打击。中巴联合研发的Link-17数据链以1毫秒延迟和动态跳频抗干扰技术&#xff0c;将预警机、战机、导…

【开源工具】音频格式转换大师:基于PyQt5与FFmpeg的高效格式转换工具开发全解析

&#x1f3a7; 【开源工具】音频格式转换大师&#xff1a;基于PyQt5与FFmpeg的高效格式转换工具开发全解析 &#x1f308; 个人主页&#xff1a;创客白泽 - CSDN博客 &#x1f525; 系列专栏&#xff1a;&#x1f40d;《Python开源项目实战》 &#x1f4a1; 热爱不止于代码&…

【Linux】环境变量完全解析

9.环境变量 文章目录 9.环境变量一、命令行参数二、获取环境变量程序中获取环境变量1. 使用命令行参数2. 使用系统调用函数getenv("字符串");3. 使用系统提供的全局变量environ 命令行中查询环境变量 三、常见环境变量1. HOME2. OLDPWD3. PATH4. SHELL 四、环境变量与…

大数据时代的利剑:Bright Data网页抓取与自动化工具共建高效数据采集新生态

目录 一、为何要选用Bright Data网页自动化抓取——帮助我们高效高质解决以下问题&#xff01; 二、Bright Data网页抓取工具 - 网页爬虫工具实测 2.1 首先注册用户 2.2 首先点击 Proxies & Scraping &#xff0c;再点击浏览器API的开始使用 2.3 填写通道名称&#xff…

【iptables防火墙】-- URL过滤 (Hexstring、IP、DoT和DoH)

在路由器中使用iptables工具对URL地址进行过滤涉及到如下几个方面&#xff0c;hexstring、ip、DoT和DoH。 以过滤www.baidu.com为例 1、DNS阻断 m string --hex-string是iptables中一个以​十六进制格式​定义要匹配的二进制特征并且支持混合明文和二进制数据的模块。由于DN…

Agent + MCP工具实现数据库查询

目录 1. RAG 2. Function Calling(函数调用) 3. MCP(模型上下文协议) 4. 案例实践 &#xff08;DifyAgent MCP数据查询&#xff09; 5. 参考资料&#xff1a; 在大模型领域里&#xff0c;RAG和Function Calling是常见的概念&#xff0c;他们之间又是有区别的&#xff0c;R…

【瑶池数据库训练营及解决方案本周精选(探索PolarDB,参与RDS迁移、连接训练营)】

一、训练营 数据库迁移训练营 自建数据库运维难&#xff1f;本次训练营教您迁移至云数据库 RDS&#xff0c;高可用架构跨区容灾&#xff0c;降本增效&#xff01;模拟教程 实战演练&#xff0c;零基础也能上手。 &#xff08;一&#xff09;开营时间 2025年4月8日-6月2日16…

005学生心理咨询评估系统技术解析:搭建科学心理评估平台

学生心理咨询评估系统技术解析&#xff1a;搭建科学心理评估平台 在心理健康教育日益受重视的当下&#xff0c;学生心理咨询评估系统成为了解学生心理状态的重要工具。该系统涵盖试卷管理、试题管理等核心模块&#xff0c;面向管理员和用户两类角色&#xff0c;通过前台展示与…

为什么企业需要应用程序可观测性

当今数字经济的持续需求迫使企业不仅要确保其应用程序功能正常&#xff0c;还必须提供高可用性、无缝扩展性和最佳性能。无论是每秒处理数百万关键交易的复杂的金融平台&#xff0c;还是服务全球多元化客户群的电商网站&#xff0c;现代企业应用程序早已突破传统简单架构&#…

Open3D 最小二乘法拟合曲线——线性回归实现

目录 1. 前言 2. 线性回归法 2.1 模型假设 2.2 定义误差函数 2.3 求偏导并解方程 2.4 案例演示 2.4.1 使用 python 实现 2.4.2 使用库函数实现(更推荐) 1. 前言 最小二乘法拟合曲线与拟合直线的核心原理完全相同,都是基于最小化误差平方和的思想,使得所有数据点到…

JavaWeb开发基础Servlet生命周期与工作原理

Servlet生命周期 Servlet的生命周期由Servlet容器(如Tomcat、Jetty等)管理&#xff0c;主要包括以下5个阶段&#xff1a; 加载Servlet类 创建Servlet实例 调用init方法 调用service方法 调用destroy方法 加载(Loading)&#xff1a; 当Servlet容器启动或第一次接收到对某个…

Electron-vite【实战】MD 编辑器 -- 系统菜单(含菜单封装,新建文件,打开文件,打开文件夹,保存文件,退出系统)

最终效果 整体架构 src/main/index.ts import { createMenu } from ./menu在 const mainWindow 后 // 加载菜单createMenu(mainWindow)src/main/menu.ts import { BrowserWindow, Menu, MenuItem, MenuItemConstructorOptions, dialog, shell } from electron import fs from…

天气预报中的AI:更准确的预测如何实现

如今的天气预报早已不是简单的看云识天气&#xff0c;而是变成了一场数据与算法的科技博弈。当你在手机App上查看未来两小时的降雨概率时&#xff0c;背后可能是AI模型分析了全球数万颗气象卫星的数据&#xff1b;当你收到台风路径预警短信时&#xff0c;或许是AI提前五天就锁定…

虚拟化数据恢复—XenServer虚拟机虚拟磁盘文件丢失的数据恢复案例

虚拟化环境&#xff1a; 某品牌720服务器中有一组通过型号为H710P的RAID卡4块STAT硬盘组建的RAID10&#xff0c;上层部署Xen Server服务器虚拟化平台。虚拟机安装的Windows Server系统&#xff0c;运行Web服务器。有系统盘 数据盘两个虚拟机磁盘。 虚拟化故障&#xff1a; 机…

Java 之殇:从中流砥柱到“被温柔替代”

—— 一位老派 Java 工程师的自述 今天看到一篇江苏的作者发出的《公司Rust团队全员被裁&#xff0c;只因把服务写得「太稳定」&#xff1a;“项目0故障、0报警&#xff0c;那养着3个Rust工程师没用啊”》帖子。看到那篇文章第一反应也是&#xff1a;这八成是 AI 编的。但说实…

vscode一直连接不上虚拟机或者虚拟机容器怎么办?

1. 检查并修复文件权限 右键点击 C:\Users\20325\.ssh\config 文件&#xff0c;选择 属性 → 安全 选项卡。 确保只有你的用户账户有完全控制权限&#xff0c;移除其他用户&#xff08;如 Hena\Administrator&#xff09;的权限。 如果 .ssh 文件夹权限也有问题&#xff0c;同…

面试中的项目经验考查:如何让实战经历成为你的决胜王牌

阅读原文 "你在项目中遇到的最大困难是什么&#xff1f;" 当面试官抛出这个问题时&#xff0c;你是否曾感到一阵心虚&#xff1f;是否担心自己的回答显得单薄无力&#xff1f;在竞争激烈的技术岗位面试中&#xff0c;项目经验往往是决定成败的关键因素。资深HR甚至建…

基于Java(SSH框架)+MySQL 实现(Web)公司通用门户(CMS)网站

一、公司通用门户网站的设计与实现 摘要&#xff1a;随着IT应用的深入普及&#xff0c;各行各业都积累了大量的信息资源&#xff0c;实现企业内部信息技术资源的有效整合和精益化管理&#xff0c;是越来越多公司企业的迫切需求。公司门户网站是一个企业向外宣传企业品牌和展示…

vue3实现鼠标悬浮div动画效果

需求 鼠标悬浮在div上显示下载按钮和信息&#xff0c;同时保持下面的div位置不变&#xff1b;当鼠标移走的时候就隐藏恢复原样。 效果&#xff1a; 代码 <script setup> const software ref([{id: "one",title: "软件",container: [{id: "123…

数据结构与算法之单链表面试题(新浪、百度、腾讯)

单链表面试题&#xff08;新浪、百度、腾讯&#xff09; 求单链表中的有效节点的个数 public int getCount(HeroNode head) {Hero1 cur head.getNext();int count 0;while(cur ! null) {count;cur cur.getNext();}return count;}查找单链表中的倒数第k个结点【新浪面试题】…