【C++杂货铺】探索stack和queue的底层实现

在这里插入图片描述

文章目录

  • 一、stack的介绍和使用
    • 1.1 stack的介绍
    • 1.2 stack的使用
      • 1.2.1 最小栈
      • 1.2.2 栈的压入、弹出序列
      • 1.2.3 逆波兰表达式求值
      • 1.2.4 用栈实现队列
  • 二、queue的介绍和使用
    • 2.1 queue的介绍
    • 2.2 queue的使用
      • 2.2.1 二叉树的层序遍历
  • 三、模拟实现
    • 3.1 stack模拟实现
    • 3.2 queue模拟实现
  • 四、容器适配器
    • 4.1 什么是适配器?
    • 4.2 STL标准库中stack和queue的底层结构
    • 4.3 deque的简单介绍
      • 4.3.1 deque的原理介绍
      • 4.3.2 deque的缺陷
    • 4.4 为什么选择deque作为stack和queue的底层默认容器?
  • 五、结语

一、stack的介绍和使用

1.1 stack的介绍

  • stack 是一种容器适配器,专门用在具有后进先出的上下文环境中。只能从容器的一端进行元素的插入与提取操作。

  • stack 是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,使得元素在特定容器的尾部(栈顶)被压入和弹出。

  • stack 的底层容器可以使任何标准的容器类模板或者一些其它特定的容器类,这些容器类应该支持以下操作:

    • empty:判空操作

    • back:获取尾部元素操作

    • push_back:尾部插入元素操作

    • pop_back:尾部删除元素操作

  • 标准容器 vector、deque、list 均符合这些需求,默认情况下,如果没有为 stack 指定特定的底层容器,默认情况下使用 deque。

在这里插入图片描述

1.2 stack的使用

函数说明接口说明
stack()构造空的栈
empty()检测栈 stack 是否为空
size()返回 stack 中元素的个数
top()返回栈顶元素的引用
push()将元素 val 压入 stack 中
pop()将 stack 中尾部的元素弹出

1.2.1 最小栈

在这里插入图片描述
本题的思路是用两个栈来实现,其中一个栈 _st 用来正常存储数据,另一个栈 _minst 用来存储最小的数据。具体实现就是在往 _st 中插入数据的时候进行判断,如果当前插入的数据 val 小于等于 _minst 栈顶的数据,那就将 val 也插入到 _minst 这个栈中。否则直将数据插入 _st 中。在 pop 数据的时候,先取 _st 的栈顶元素和 _minst 的栈顶元素进行比较,如果二者相等,那就同时 pop _st_minst 的栈顶元素,否则就值 pop _st 的栈顶元素。要获取堆栈中的最小元素直接返回 _minst 的栈顶元素即可。

class MinStack 
{
public:MinStack() {}void push(int val) {_st.push(val);if(_minst.empty() || val <= _minst.top()){_minst.push(val);}}void pop() {if(_st.top() == _minst.top()){_minst.pop();}_st.pop();}int top() {return _st.top();}int getMin() {return _minst.top();}
private:stack<int> _st;stack<int> _minst;
};

1.2.2 栈的压入、弹出序列

在这里插入图片描述
本题的解题思路是用一个栈来模拟。即先定义一个栈 st 然后给栈中入一个数据,接着取栈顶的数据和出栈序列 popV 当前位置元素进行比较进行比较,如果不相等则继续从入栈序列 pushV 中拿数据往栈 st 中入,如果相等就出栈。这里需要注意,有可能需要连续多次出栈。直到最终将入栈序列 pushV 中的数据全入栈,最后判断栈 st 是否为空,如果为空,就说明该出栈序列正确。如果不空就说明该出栈序列有问题。

class Solution {
public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param pushV int整型vector * @param popV int整型vector * @return bool布尔型*/bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {// write code herestack<int> st;size_t push_pos = 0, pop_pos = 0;while(push_pos < pushV.size()){//先入一个元素st.push(pushV[push_pos++]);if(st.top() != popV[pop_pos]){//不匹配继续入数据continue;}while(!st.empty() && st.top() == popV[pop_pos]){//匹配,出数据st.pop();pop_pos++;}}return st.empty();}
};

代码优化:我们可以发现上面代码中不匹配逻辑里面其实啥也没干,因此我们可以把这段代码给删掉,上面加上是为了使逻辑更加清晰。

class Solution {
public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param pushV int整型vector * @param popV int整型vector * @return bool布尔型*/bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {// write code herestack<int> st;size_t push_pos = 0, pop_pos = 0;while(push_pos < pushV.size()){//先入一个元素st.push(pushV[push_pos++]);while(!st.empty() && st.top() == popV[pop_pos]){//匹配,出数据st.pop();pop_pos++;}}return st.empty();}
};

1.2.3 逆波兰表达式求值

在这里插入图片描述
逆波兰表达式也被叫做后缀表达式。什么是后缀表达式呢?先来了解一下中缀表达式,中缀表达式就是我们平时最常见的,例如: 2 + 3 ∗ 1 2+3*1 2+31,就是一个典型的中缀表达式。将前面的中缀表达式变成后缀表达式得到:2 1 3 * +,这就是一个后缀表达式,后缀表达式相较于中中缀表达式,操作数顺序不变,操作符按优先级重排。这道题目就是要求我们对后缀表达式进行求解。求解后缀表达式,我们可以借助一个栈,遇到操作数入栈,遇到操作符从栈中出两个元素进行运算,将运算结果继续入栈。最终栈顶的元素就是整个逆波兰表达式的结果。

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;size_t pos = 0;while(pos < tokens.size()){if(tokens[pos] != "+" && tokens[pos] != "-" && tokens[pos] != "*" && tokens[pos] != "/"){//如果是数字就入栈int num = stoi(tokens[pos]);st.push(num);}else{//不是数字就从栈中取两个元素出来int val1 = st.top();st.pop();int val2 = st.top();st.pop();int ret = 0;if(tokens[pos] == "+"){ret = val2 + val1;}else if(tokens[pos] == "-"){ret = val2 - val1;}else if(tokens[pos] == "*"){ret = val2 * val1;}else if(tokens[pos] == "/"){ret = val2 / val1;}//将计算结果继续入栈st.push(ret);}pos++;}return st.top();}
};

注意:在写上面这段代码的时候有下面几点需要特别注意,首先这是一个 string 数组,会涉及到 stringint 。其次需要注意在从栈中取数的时候,第一次取出的是右操作数,第二次取出的是左操作数,因此 val2 应该做左操作数,val1 应该做右操作数,尤其是减法运算和除法运算,这两个操作数的顺序必须得到保证。

补充:这里补充一个小知识点:如何将中缀表达式转换成后缀表达式。主要过程分为以下几步:

  1. 遇到操作数就输出(这里的输出是将其存储到某种容器里)。

  2. 遇到操作符,根据优先级的顺序分为以下两种情况:

    • 栈为空或当前操作符比栈顶的优先级高,继续入栈。

    • 栈不为空且当前操作符比栈顶的优先级低或者相等,则输出栈顶操作符,继续执行第二步。

  3. 中缀表达式结束后,依次出栈里面的操作符。

小Tips:当前操作符能否计算取决于后一个操作符的优先级是否高于自己,所以每当我们遇到一个操作符的时候,先不着急将它入栈,先和栈顶的操作符进行优先级比较,如果当前操作符的优先级比栈顶操作符的优先级低或者相等,我们就可以取出栈顶这个操作符进行运算。如果遇到括号可以走一个递归。其次就是需要想办法确定符号的优先级。

1.2.4 用栈实现队列

在这里插入图片描述
将一个栈当做输入栈,用于压入 push 传入的数据,另一个栈当做输出栈,用于 pop 和 peek 操作。每次 pop 或 peek 时,若输出栈为空则将输入栈的全部数据依次弹并压入输出栈,这样输出栈从栈顶往栈底的顺序就是队列从队首往队尾的顺序。

class MyQueue {
public:MyQueue() {}void push(int x) {input_st.push(x);}int pop() {if(output_st.empty()){while(!input_st.empty()){output_st.push(input_st.top());input_st.pop();} }int ret = output_st.top();output_st.pop();return ret;}int peek() {if(output_st.empty()){while(!input_st.empty()){output_st.push(input_st.top());input_st.pop();}}return output_st.top();}bool empty() {return input_st.empty() && output_st.empty();}
private:stack<int> input_st;stack<int> output_st;
};

二、queue的介绍和使用

2.1 queue的介绍

  • 队列是一种容器适配器,专门用于在FIFO上下文中执行先进先出操作,其中从容器的一端插入元素,另一端提取元素。

  • 队列作为容器适配器实现,容器适配器即将特定的容器类封装最为其底层容器,queue 提供一组特定的成员函数来访问其元素。元素从队尾入队列,从对头出队列。

  • 底层容器可以是标准容器模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:

    • empty:检查队列是否为空

    • size:返回队列中有效元素的个数

    • front:返回对头元素的引用

    • back:返回队尾元素的引用

    • push_back:在队列尾部入队列

    • pop_front:在队列头部出队列

  • 标准容器类 deque 和 list 满足了这些要求。默认情况下,如果没有为 queue 实例化指定容器类,则使用标准容器 deque。

在这里插入图片描述

2.2 queue的使用

函数声明接口说明
queue()构造空队列
empty()检测队列是否为空,是返回 true,否则返回 false
size()返回队列中有效元素个数
front()返回队头元素的引用
back()返回队尾元素的引用
push()在队尾将元素 val 入队列
pop()将队头元素出队列

2.2.1 二叉树的层序遍历

在这里插入图片描述
二叉树的层序遍历可以借助队列来实现,从根节点开始,先让父节点入队列,在出队尾节点的同时,将该节点的左孩子和右孩子依次入队列。直到队列为空,从队列中出出来的结果就是层序遍历的结果。这道题目需要将同一层的所有节点都存入一个一维数组,再将这些一维数组组合成一个二维数组返回。我们前面的这种做法会导致队列中出现两层节点混在意的情况,因此我们可以定义一个变量 levelSize 来记录每一层的节点数。具体代码如下:

class Solution 
{
public:vector<vector<int>> levelOrder(TreeNode* root) {vector<vector<int>> retV;queue<TreeNode*> qu;int levelSize = 0;qu.push(root);while(!qu.empty()){vector<int> tmp;levelSize = qu.size();while(levelSize != 0){TreeNode* top = qu.front();if(top != nullptr){tmp.push_back(top->val);qu.push(top->left);qu.push(top->right);} qu.pop();levelSize--;}if(!tmp.empty()){retV.push_back(tmp);}}return retV;}
};

三、模拟实现

3.1 stack模拟实现

template<class T, class Continer = vector<T>>class stack{public:stack(){}void push(const T& val){_con.push_back(val);}void pop(){_con.pop_back();}T& top(){return _con.back();}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Continer _con;};

小Tips:stack 可以使用 vector 或者 list 来实现,效率相当。插入数据就相当于尾插,删除栈顶元素就相当于尾删。

3.2 queue模拟实现

template<class T, class Continer = std::list<T>>
class queue
{
public:queue(){}void push(const T& val){_con.push_back(val);}void pop(){_con.pop_front();//这里不再支持vector}T& front(){return _con.front();}T& back(){return _con.back();}size_t size(){return _con.size();}bool empty(){return _con.empty();}
private:Continer _con;
};

小Tips:栈不能借助 vector 来实现,因为出队列,相当于删除 vector 中的第一个元素,而对 vector 头删会涉及挪动数据,效率相较于 list 会有所下降。

四、容器适配器

4.1 什么是适配器?

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

4.2 STL标准库中stack和queue的底层结构

虽然 stack 和 queue 中也可以存放元素,但在 STL 中并没有将其划分在容器行列,而是将其称为容器适配器,这是因为 stack 和 queue 只是对其他容器的接口进行了包装,STL 中 stack 和 queue 默认使用 deque。

4.3 deque的简单介绍

4.3.1 deque的原理介绍

deque(双端队列)是一种双开口的“连续”空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与 vector 比较,头插效率高,不需要搬移元素;与 list 比较,空间利用率比较高。
在这里插入图片描述
deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际 deque 类似于一个动态的二维数组,其底层结构如下图所示:
在这里插入图片描述
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了 deque 的迭代器身上,因此 deque 的迭代器设计的就比较复杂,如下图所示:
在这里插入图片描述
在这里插入图片描述

4.3.2 deque的缺陷

  • 与 vector 比较,deque 的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是比 vector 高的。

  • 与 list 比较,其底层空间是连续的,空间利用率比较高,不需要存储额外字段。

  • 但是,deque 有一个致命缺陷:不适合遍历,因为在遍历时,deque 的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构式,大多数情况下优先考虑 vector 和 list,deque 的应用并不多,而目前能看到的一个应用场景就是,STL 用其作为 stack 和 queue 的底层数据结构。

4.4 为什么选择deque作为stack和queue的底层默认容器?

stack 是一种后进先出的特殊线性数据结构,因此只要是具有 push_back() 和 pop_back() 操作的线性结构,都可以作为 stack 的底层容器,比如 vector 和 list 都可以;queue 是先进先出的特殊线性数据结构,只要具有 push_back() 和 pop_front() 操作的线性结构,都可以作为 queue de 底层容器,比如 list。但是 STL 中对 stack 和 queue 默认选择 deque 作为其底层容器,主要是因为:

  • stack 和 queue 不需要遍历(因此 stack 和 queue 没有迭代器),只需要在固定的一端或者两端进行操作。

  • 在 stack 中元素增长时,deque 比 vector 的效率高(扩容时不需要搬移大量数据);queue 中的元素增长时,deque 不仅效率高,而且内存利用率高。

五、结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,春人的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是春人前进的动力!

在这里插入图片描述

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

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

相关文章

CUDA小白 - NPP(4) 图像处理 Data Exchange and Initialization(1)

cuda小白 原始API链接 NPP GPU架构近些年也有不少的变化&#xff0c;具体的可以参考别的博主的介绍&#xff0c;都比较详细。还有一些cuda中的专有名词的含义&#xff0c;可以参考《详解CUDA的Context、Stream、Warp、SM、SP、Kernel、Block、Grid》 常见的NppStatus&#xf…

二叉树(上)

“路虽远&#xff0c;行则将至” ❤️主页&#xff1a;小赛毛 目录 1.树概念及结构 1.1树的概念 1.2 树的相关概念 1.3 树的表示&#xff08;树的存储&#xff09; 2.二叉树概念及结构 2.1概念 2.2现实中的二叉树 2.3 特殊的二叉树&#xff1a; 2.4 二叉树的性质 3.二叉树的顺…

java 整合 swagger-ui 步骤

1.在xml 中添加Swagger 相关依赖 <!-- springfox-swagger2 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><!-- springfox-swa…

【Python从入门到进阶】35、selenium基本语法学习

接上篇《34、selenium基本概念及安装流程》 上一篇我们介绍了selenium技术的基础概念以及安装和调用的流程&#xff0c;本篇我们来学习selenium的基本语法&#xff0c;包括元素定位以及访问元素信息的操作。 一、元素定位 Selenium元素定位是指通过特定的方法在网页中准确定位…

Canape使用中遇到问题的解决办法

问题一、canape绘制目标时&#xff0c;二维结构体变量只能录制16个的解决办法 打开ASAP2 Studio 2.6 -Expert 软件。 把该软件中的设置项如下图进行修改。 然后用ASAP2 Studio 2.6 -Expert 软件 打开elf文件导出成A2L文件。 最后关闭该软件。 再在canape工程中重新加载刚才…

振弦采集仪应用地铁隧道安全监测详细解决方案

振弦采集仪应用地铁隧道安全监测详细解决方案 随着城市化进程的不断加快&#xff0c;地铁作为一种高效、便捷、环保的交通方式已经成为现代城市不可或缺的一部分。因此&#xff0c;对地铁的安全性也越来越重视&#xff0c;一般二三线以上的城市在不断发展中&#xff0c;地铁做…

c语言 2.0

1.数据类型 数据类型介绍 数据类型&#xff1a;c语言中数据类型有3种&#xff0c;分别是基本数据类型、构造数据类型、指针数据类型。 数据类型的作用&#xff1a;编译器预算数据分配的内存空间大小。 ps&#xff1a;可以通俗理解为&#xff1a;数据类型是用来规范内存的开销…

初识docker

目录 docker解决的问题1. 开发、测试和运维人员之间的矛盾2. 更轻量的虚拟化&#xff0c;节省了虚拟机的性能损耗 虚拟机与容器的区别1. 虚拟机2. 容器 Docker 系统架构 docker解决的问题 1. 开发、测试和运维人员之间的矛盾 “程序在我这跑得好好的&#xff0c;在你那怎么就…

C++之红黑树

红黑树 红黑树的概念红黑树的性质红黑树结点的定义红黑树的插入红黑树的验证红黑树与AVL树的比较 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上…

uniapp 在 onLoad 事件中 this.$refs 娶不到的问题

现象 本人想在主页面加载的时候调用子组件的方法。示例代码如下&#xff1a; 运行&#xff0c;发现 this.$refs 取不到。如下图所示&#xff1a; 解决方法&#xff0c;把onLoad 换为 onReady 就可以了。

【Linux】管道

管道命令 #include <unistd.h> int pipe(int pipefd[2]); 在Linux中&#xff0c;管道&#xff08;pipe&#xff09;的返回值是一个整数数组&#xff0c;包含两个文件描述符。这两个文件描述符分别代表管道的读端和写端。 当成功创建一个管道时&#xff0c;pipe() 系统调用…

K8S:kubectl陈述式及声明式资源管理

文章目录 一.陈述式资源管理方法1.陈述式资源管理概念2.基本信息查看&#xff08;1&#xff09;查看版本信息&#xff08;2&#xff09;查看资源对象简写&#xff08;3&#xff09;查看集群信息&#xff08;4&#xff09;配置kubectl自动补全&#xff08;5&#xff09;node节点…

基于Linux并结合socket网络编程的ftp服务器的实现

项目需求 客户端能够通过调用“get”指令&#xff0c;来获取服务器的文件客户端能通过“server_ls”指令&#xff0c;来获取服务器路径下的文件列表客户端能通过“server_cd”指令&#xff0c;进入服务器路径下的某文件夹客户端可以通过“upload”指令&#xff0c;上传自己的本…

Redis发布订阅机制学习

【IT老齐151】Redis发布订阅机制是如何实现的&#xff1f;_哔哩哔哩_bilibili go-redis的发布与订阅 - 知乎 (zhihu.com) 前置&#xff1a; 先输入 redis-server.exe 启动redis&#xff0c;否则对应接口不开放 再输入 redis-cli 命令启动客户端服务 1.机制示意图 当一…

AR产业变革中的“关键先生”和“关键力量”

今年6月的WWDC大会上&#xff0c;苹果发布了头显产品Vision Pro&#xff0c;苹果CEO库克形容它&#xff1a; 开启了空间计算时代。 AR产业曾红极一时&#xff0c;但因为一些技术硬伤又减弱了声量&#xff0c;整个产业在起伏中前行。必须承认&#xff0c;这次苹果发布Vision P…

七大排序算法

目录 直接插入排序 希尔排序 直接选择排序 堆排序 冒泡排序 快速排序 快速排序优化 非递归实现快速排序 归并排序 非递归的归并排序 排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作. 常见的排序算法有插入排序(直接插入…

队列(Queue)的顶级理解

目录 1.队列(Queue) 的概念 2.单链表模拟实现队列 2.1创建队列 2.2入队列 2.3判断是否为空 2.4出队列 2.5获取队头元素 2.6完整代码&#xff1a; 2.7双向链表模拟实现队列代码 3.数组模拟实现队列代码 3.1创建队列 3.2判断是否为满 3.3检查是否为空 3.4插入元素 3…

ctfshow 反序列化

PHP反序列化前置知识 序列化和反序列化 对象是不能在字节流中传输的&#xff0c;序列化就是把对象转化为字符串以便存储和传输&#xff0c;反序列化就是将字符串转化为对象 魔术方法 __construct() //构造&#xff0c;当对象new时调用 __wakeup() //执行unserialize()时&am…

mysql 增量备份与恢复使用详解

目录 一、前言 二、数据备份策略 2.1 全备 2.2 增量备份 2.3 差异备份 三、mysql 增量备份概述 3.1 增量备份实现原理 3.1.1 基于日志的增量备份 3.1.2 基于时间戳的增量备份 3.2 增量备份常用实现方式 3.2.1 基于mysqldump增量备份 3.2.2 基于第三方备份工具进行增…

【QT】QMessageBox消息框的使用(16)

在实际项目中&#xff0c;弹出消息框是一个很常见的操作&#xff0c;包含错误信息提示、警告信息提示、关于信息提示、还包括判断信息选择等操作&#xff0c;那么今天通过这一节来好好了解下消息框的使用方法。 一.环境配置 1.python 3.7.8 可直接进入官网下载安装&#xf…