文章目录
目录
文章目录
前言
一、stack、queue介绍
1.stack
2.queue
二、stack、queue的习题
1. 最小栈
2. 栈的压入、弹出序列
3.二叉树的层序遍历
三、stack和queue的模拟实现
1.stack的模拟实现
2.queue的模拟实现
前言
栈和队列是俩种特殊的容器,C++在实现栈和队列时,复用了vector和list容器。本章内容我们将介绍和模拟实现stack(栈)和queue(队列)。以及做几道关于stack和queue的题。加深对stack和queue的理解。
一、stack、queue介绍
1.stack
stack的文档介绍https://legacy.cplusplus.com/reference/stack/stack/?kw=stack
翻译:
1. stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
2. stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。
3. stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:
empty:判空操作
back:获取尾部元素操作
push_back:尾部插入元素操作
pop_back:尾部删除元素操作
4. 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque。
stack的接口:
函数说明 | 接口说明 |
stack() | 构造空的栈 |
empty() | 检测stack是否为空 |
size() | 返回stack中元素的个数 |
top() | 返回栈顶元素的引用 |
push() | 将元素val压入stack中 |
pop() | 将stack中尾部的元素弹出 |
2.queue
queue - C++ Reference (cplusplus.com)https://legacy.cplusplus.com/reference/queue/queue/翻译:
1. 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。
2. 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。
3. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:
empty:检测队列是否为空
size:返回队列中有效元素的个数
front:返回队头元素的引用
back:返回队尾元素的引用
push_back:在队列尾部入队列
pop_front:在队列头部出队列
4. 标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。
queue的接口:
函数声明 | 接口说明 |
queue() | 构造空的队列 |
empty() | 检测队列是否为空,是返回true,否则返回false |
size() | 返回队列中有效元素的个数 |
front() | 返回队头元素的引用 |
back() | 返回队尾元素的引用 |
push() | 在队尾将元素val入队列 |
pop() | 将队头元素出队列 |
二、stack、queue的习题
1. 最小栈
155. 最小栈 - 力扣(LeetCode)https://leetcode.cn/problems/min-stack/
思路:使用俩个栈,一个栈用于存储所有数据,另一个栈用于记录动态记录最小数据(栈顶为最小数据).
1.如果st为空,第一次Push时,minst也应该push。
2.在之后的每一次push,都要与minst的栈顶元素进行比较,如果<=minst栈顶元素,minst就也需要push该数据。否则只需要push到st。如图,push了1、2、0.最小的就是minst栈顶元素。
(等于的时候minst也要push的原因,是因为pop时,如果有俩个相同的最小元素,最终最小元素不会记录):
如图再次push0,但未记录,pop 0 时minst栈顶结果不对。
3.在每次pop的时候和minst栈顶元素进行比较,如果等于栈顶元素则minst也需要pop掉。(因为在2步骤我们保证了minst的栈顶是最小值)
代码:
class MinStack {
public:
MinStack() {
//构造可以不写,因为stack是自定义类型会自动调用它的构造。
}
void push(int val) {
//minst为空minst push。不为空进行比较
st.push(val);
if(minst.empty() || st.top() <= minst.top())
{
minst.push(val);
}
}
void pop() {
//和minst栈顶元素相同minst pop
if(st.top() == minst.top())
{
minst.pop();
}
st.pop();
}
int top() {
return st.top();
}
int getMin() {
return minst.top();
}
stack<int> st;
stack<int> minst;
};
2. 栈的压入、弹出序列
栈的压入、弹出序列_牛客题霸_牛客网 (nowcoder.com)https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&&tqId=11174&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking
已知入栈和出栈序列:vector<int>
首先我们需要有一个栈,按照入栈序列的顺序进行入栈。
思路:
1.先入一个栈。
2.s的栈顶元素和popV的栈顶元素(顺序就是出栈顺序)比较:
a.如果相同,出栈序列往后走,栈顶元素pop掉。(说明以及匹配了出栈的顺序)
相同了,pop掉4,popi往后走。
b.如果不相同,回到第1步,继续入栈,然后比较。知道pushV入完。下面是不相同:
结束条件:
遍历pushV有俩种结果:
1.遍历完pushV,popV结束,st为空
2.遍历往pushV,st不为空,popV没走完。
代码:
class Solution {
public:
bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
// write code here
size_t popi = 0, pushi = 0;
stack<int> s;
for(; pushi < pushV.size(); pushi++)
{
//先入一个
s.push(pushV[pushi]);
//进行比较
while(!s.empty() && s.top() == popV[popi])
{
//如果出栈和popV匹配,则s出栈,比较下一个出栈的。
popi++;
s.pop();
}
//如果不匹配
//接着入栈
}
//结束条件,俩种结果都要遍历完pushV。
//最后可以判断st是否为空。或者popi是否等于出栈序列的大小
return s.empty();
//return popi == popV.size();
}
};
3.二叉树的层序遍历
102. 二叉树的层序遍历 - 力扣(LeetCode)https://leetcode.cn/problems/binary-tree-level-order-traversal/description/二叉树的层序遍历要使用到队列。
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> q;
q.push(root);//插入头节点
while(!q.empty())
{
//头节点出
TreeNode* front = q.front();
q.pop();
//孩子节点入.不为空再入
if(front->left)
q.push(front->left);
if(front->right)
q.push(front->right);
}
}
};
思路:使用一个levesize变量控制一层一层出。
第一层:1个数据。
levesize = 1;
第一层出完之后:
下一层的个数是q.size();
重置levesize = q.size();
第二层是:第一层孩子的个数。
所以控制levesize--。就可以将每一层的储存到二维数组里。
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> vv;
if(root == nullptr)
{
return vv;
}
queue<TreeNode*> q;
q.push(root);//插入头节点
int levelsize = 1;
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);
//下一层的个数是q.size()
levelsize = q.size();
}
return vv;
}
};
三、stack和queue的模拟实现
1.stack的模拟实现
从栈的接口中可以看出,栈实际是一种特殊的vector,因此使用vector完全可以模拟实现stack。
#include<vector>
namespace mystack
{
template<class T>
class stack
{
public:
stack() {};
void push(const T& x) {_c.push_back(x);}
void pop() {_c.pop_back();}
T& top() {return _c.back();}
const T& top()const {return _c.back();}
size_t size()const {return _c.size();}
bool empty()const {return _c.empty();}
private:
std::vector<T> _c;
};
}
像上面这样修改起来不方便,可以增加模板参数:
#include <iostream>
using namespace std;
#include <vector>
#include <list>
#include <deque>
namespace mystack
{
template<class T, class Container = vector<T>>
class stack
{
public:
stack(){};
//入栈
void push(const T& data)
{_con.push_back(data);}
//出栈
void pop()
{_con.pop_back();}
//栈顶数
T top()
{return _con.back();}
//判空
bool empty()
{return _con.empty();}
//数据
size_t size()
{return _con.size();}
private:
Container _con;
};
}
2.queue的模拟实现
因为queue的接口中存在头删和尾插,因此使用vector来封装效率太低,故可以借助list来模拟实现queue,具体如下:
#include <iostream>
using namespace std;
#include <vector>
#include <list>
#include <deque>
namespace myqueue {
template<class T, class Container = list<T> >
class queue
{
public:
queue() {};
//插入
void push(const T& data)
{_con.push_back(data);}
//pop
void pop()
{_con.pop_front();}
T& front()
{return _con.front();}
T& back()
{return _con.back();}
const T& front()
{return _con.front();}
const T& back()
{return _con.front();}
//empty
bool empty()
{return _con.empty();}
//size
size_t size()
{return _con.size();}
private:
Container _con;
};
}
如果你有所收获,可以留下你的点赞和关注。谢谢你的观看!!!