博主我也是好久没更新了,忙于期末考试。现在为大家带来C++STL的**stack,queue,**的超级详解!
目录
[3.1 最小栈](#3.1 最小栈)
[4.stack 和 queue 的底层实现](#4.stack 和 queue 的底层实现)
[4.1stack 和 container 容器适配器](#4.1stack 和 container 容器适配器)
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下一篇详细讲解,涉及内容较多。请多多点赞,谢谢大家