C++STL:stack,queue,详解!!:OJ题练手使用和手撕底层代码

博主我也是好久没更新了,忙于期末考试。现在为大家带来C++STL的**stack,queue,**的超级详解!

目录

1.stack

1.1栈和队列没有迭代器

1.2栈的断言检查

2.queue

2.1队列的使用和遍历

3.栈与队列的OJ题练手

[3.1 最小栈](#3.1 最小栈)

3.1.1思路讲解

3.1.2代码展示

3.2栈的压入、弹出序列

3.2.1思路讲解

3.2.2代码演示

3.3逆波兰表达式求值

3.3.1思路讲解

3.3.2代码演示

3.4二叉树的层序遍历

3.4.1思路讲解

3.4.2代码演示

[4.stack 和 queue 的底层实现](#4.stack 和 queue 的底层实现)

[4.1stack 和 container 容器适配器](#4.1stack 和 container 容器适配器)

5.queue


1.stack

stack也是一个容器,他与之前学的略有不同:

stack的第二个模板参数是一个容器,container,也有适配器的意思,原先list是内存池,相比new效率略高.deque是双端队列,这说明stack底层其实是由其他容器适配出来的。具体到底层再了解。

栈是一种数据结构,LIFO(后进先出)的数据结构,对于他的使用:

和之前的学的容器十分相似,用法也一样, top 是取栈顶元素。emplace 现阶段等同于push,之前讲过区别。

1.1栈和队列没有迭代器

不过,栈和队列都没有迭代器。因为栈是严格按照后进先出,迭代器无法进行这个保障。可以通过取栈,出栈的循环实现打印,边删边出。

1.2栈的断言检查

如标题,栈是有断言检查的。当栈为空,此时进行 pop 操作,断言检查会直接中止程序。

2.queue

queue,队列,是一种FIFO(先进先出)的数据结构,其模板参数和栈一样,缺省值为双端队列deque

2.1队列的使用和遍历

队列的核心成员函数就是取对头,取队尾。其余一样,遍历打印和 stack 的一样。

3.栈与队列的OJ题练手

3.1 最小栈

链接:最小3.2栈

3.1.1思路讲解

在栈的基础上,这题要求了一个新接口,getMin获取堆栈中最小元素,并且是O(1)时间复杂度。

常规想法是:创建一个 栈 和 min变量 ,min记录最小的值,每次 push 新数据,min记录更小的数据,最后返回min

缺陷 :如果 pop 数据,min无法更新,如果删除是最小的数据,此时min丢失了最小数据,要想重新获取,需要遍历一遍,时间复杂度高,并且栈很难遍历

更优的思路是:。创建一个两个栈,st存值,minst存最小的值,当存入的值<=当前minst栈顶值,存入minst,看图理解:

图1 图2

如图,大于当前栈顶的值不会存入minst,而小于的会存,minst的栈顶是越来越小的。重复的最小值也得存入minst,不然pop st最小值的时候,minst跟着 pop(st删除了最小值,minst理应一起删除) ,导致丢失了1这个最小值(原st还存在重复的最小值)

(图2)

3.1.2代码展示

私有成员就是两个栈。注意此时不需要显示写构造函数,因为对于自定义类型,他会调用他自己的构造函数。

minst如果为空,则无论如何先插入一个值。当st插入的值<=minst栈顶时,说明此时新的val更小,则把val同步插入到minst,维持minst栈顶最小。

当st要删除的是当前栈最小值,则minst同步删除。否则直接删除即可。

直接返回当前st的栈顶就行。

minst栈顶存的就是当前最小值,一直同步于st

3.2栈的压入、弹出序列

链接:栈的压入、弹出序列

3.2.1思路讲解

这道题的核心思路就是模拟流程,题目给了两个vector动态数组,我们需要创建一个栈,模拟他的流程,如果最终能得到正确结果,那就是正确,否则错误。
例如图中的示例1:压栈是1 2 3 4 5,出栈是4 5 3 2 1 ,既然第一个出4,那压栈就是压到1 2 3 4,然后出4,压5,出5,最后出3 2 1 ,就能得到答案

示例2:压到4,出4 3,压5,出5,然后理应继续出的是2 1 ,但是题目中是出1 2,所以不符合要求,是错误的。
分析了两个示例,得知需要遍历两个序列,然后根据要求压栈出栈序列,那就需要两个指针,vector下标可充当伪指针。然后当压栈序列元素不等于出栈序列元素(后简称压出元素),则一直压栈。当压出元素相等,则要出栈。但是出栈是连续的,所以需要while循环,条件就是当压出元素不相等,并且栈不为空(空则无法出栈了),同时,两个伪指针同时自增。

当栈为空,即全部顺利压栈,出栈,则表示序列无问题,返回 栈判空。空即true。

3.2.2代码演示

大部分和上面的解释一样。设置了pushi和popi,当pushi全部压入栈后结束。最后如果栈内没有清空,那就是错误序列。

3.3逆波兰表达式求值

链接:逆波兰表达式求值

逆波兰表达式,也就是后缀表达式。 像我们中国人常用的1+1,就是中缀表达式

图中示例:2 1 + 3 * ,2 和 1的 和,作为一个操作数 然后与 3 的乘积 答案9

3.3.1思路讲解

题目给字符串数组vector<string> ,里面每个字符串作为一个 操作数 或者 数字 ,这里的思路就是,遍历数组,然后如果是数字就压栈,如果是操作数,就取出栈顶元素,第一个是右操作数,第二个是左操作数,然后把计算结果压入栈中,继续循环。循环结束后,就是完成了计算,最终返回栈顶元素就是计算结果。

3.3.2代码演示

注意数字存入栈中需要用stoi函数(string to int)把字符串的数字转换为整型类型(不是ASCII码值),存入栈中

然后,遍历tokens需要范围for,简便一点。

复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        
        for(auto& c : tokens)
        {
            if(c =="+"||c =="-"||c =="*"||c =="/")
            {
                //运算符
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                if(c=="+") st.push(left+right);
                if(c=="-") st.push(left-right);
                if(c=="*") st.push(left*right);
                if(c=="/") st.push(left/right);
            }
            else
            {
                //数字
                st.push(stoi(c));
            }
        }
        return st.top();
    }
};

3.4二叉树的层序遍历

链接:二叉树的层序遍历

3.4.1思路讲解

二叉树的层序遍历,利用了queue。题目给了二维动态数组vector<vector<int>>作为参数,接收每层的数据,实现层序遍历,先把根节点入队,然后根据根节点引出其子节点,根节点出队。再根据子节点引出子节点的子节点,如此循环下去直到队内没有节点,说明遍历结束。每次遍历把引出的节点入队,下次出队时插入到动态一维数组中,这一层结束后,把一维数组插入到二维数组中,这样一层就遍历完了。

根节点可能引出2个子节点,那子节点2给可能引出4个节点,怎么控制变量让每次循环刚好完成对应次数呢?此时需要一个levelSize,层节点数量,入队几个就赋值几,然后循环让levelSize-- >0,就可以实现对应次数的循环操作。

3.4.2代码演示

先定义一个存节点的队列,把根节点存入,并赋值levelSize = 1,后面大循环内,存入根节点的左右子节点,然后把根节点的值存入数组v,循环结束v插入vv中,更新levelSize为q.size(),因为子节点存入了队列,队列长度代表子节点的个数,也代表循环次数。

复制代码
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        size_t levelSize = 0;
        queue<TreeNode*> q;
        if(root)
        {
            q.push(root);
            levelSize=1;
        }
        vector<vector<int>> vv;
        while(!q.empty())
        {
            vector<int> v;
            while(levelSize--)
            {
                TreeNode* front = q.front();
                q.pop();
                v.push_back(front->val);
                
                if(front->left) q.push(front->left);
                if(front->right) q.push(front->right);
            }
            vv.push_back(v);
            levelSize=q.size();
        }
        return vv;
    }
};

4.stack 和 queue 的底层实现

4.1stack 和 container 容器适配器

根据我们常规的实现,大概就是这样:数组,空间,栈顶

但库里的栈没有这么实现,因为相似度很高,他是复用了其他容器的。比如要实现数组栈,我们直接封装vector实现就行,思路是这么个思路,但是库里还要更高级:

直接写vector就固定死了,库里是用适配器:container,这样就不局限于vector,也可以用list,或者其他。所以container是叫容器适配器,可以转换的意思。

那接下来,push,pop等函数的实现就是复用 _con 的push 或 pop:

这样,stack的核心成员就实现了。 注:vector其实是有front和back的,返回头或尾,平时不太用,大概就是为这时候准备的

如果所给容器不支持push_back,pop_back等操作怎么办?

那就报错,说明此容器不适配,换一个就行了。

通过以上操作,我们就实现了复用,可以传list,或者vector,实现数组栈,链式栈。

库里用了缺省参数:deque

这说明deque也是适配stack的接口的,deque等会我们再细说,这里简单介绍一下:

deque可以认为是vector和list的结合体吗,所以作为默认是完全没问题的

5.queue

queue队列,实现逻辑和stack几乎一样,但是队列是FIFO先进先出,所以pop要改成pop_front

队列就实现好了,在stack基础上改几个成员函数就行,栈顶改成队头,添加一个取队尾。

注意:

queue不能用vector作为容器,因为queue需要删队头,但是vector没提供头删。erase可以吗?

当然可以,但是效率很低,没必要。


deque下一篇详细讲解,涉及内容较多。请多多点赞,谢谢大家

相关推荐
fy zs9 小时前
应用层自定义协议和序列化
linux·网络·c++
全栈小59 小时前
【前端】在JavaScript中,=、==和===是三种不同的操作符,用途和含义完全不同,一起瞧瞧
开发语言·前端·javascript
这里是彪彪10 小时前
Java中的volatile关键字的作用
java·开发语言
Dxy123931021610 小时前
Python的zip用法详解
开发语言·python
逑之10 小时前
C语言笔记3:分支与循环
c语言·开发语言·笔记
黎雁·泠崖10 小时前
Java入门从零起步:CMD操作+JDK环境搭建+第一个Java程序
java·开发语言
f***241110 小时前
MATLAB高效算法优化实战指南
开发语言·算法·matlab
智算菩萨10 小时前
【Python自然语言处理】实战项目:词向量表示完整实现指南
开发语言·python·自然语言处理
CSDN_RTKLIB10 小时前
【std::vector】避免频繁扩容方法
c++·stl