Stack
一.有关stack介绍
- stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其只能从容器的一端进行元素的插入、提取或者删除操作。
- stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定
的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。 - stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下
操作:
empty:判空操作
back:获取尾部元素操作
push_back:尾部插入元素操作
pop_back:尾部删除元素操作 - 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,
默认情况下使用deque。
二.适配器模式下stack的实现
1.设计模式
2.实现stack的代码
Stack.h头文件的实现:
#include<iostream>
#include<vector>
#include<list>
#include<deque>
using namespace std;
//栈后进先出的特性
namespace bit
{
template<class T,class Container = vector<T>>
//在使用vector做模板时,不能只引入#include<vector>,还要引入using namespace std;
class Stack
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
T top()
{
return _con.back();
}
private:
Container _con;
};
}
测试类的实现
#include<iostream>
#include"Stack.h"
int main()
{
bit::Stack<int> st;
st.push(1);
st.push(2);
st.push(3);
st.push(4);
st.push(5);
st.push(6);
while (!st.empty())
{
cout << st.top() << endl;
st.pop();
}
return 0;
}
三.有关stack的经典题
力扣题一:最小栈
https://leetcode.cn/problems/min-stack/
解法一:利用两个栈,第一个栈用来输入数列,第二个栈是对应第一个栈的每个元素对应的最小值
解法二:利用两个栈,第一个栈用来输入数列,第二个栈仍旧是输入最小值,但不是对应第一个栈的每个元素。只有最小值改变时才会压栈到第二个栈,并且如果最小值等于第一个栈的某元素,要重复压栈。
较第一种解法,空间消耗要低。
解法三:利用两个栈,第一个栈用来输入数列,第二个栈是存储一个结构体元素。该结构体分为两部分,一部分是最小值,第二部分是最小值的计数。
当第一个栈某个元素与当前最小值相等时,计数就加1。
该解法空间消耗率是比第二种要更低。
在这里,我们只显示第三种解法的代码。
#include<iostream>
using namespace std;
#include<stack>
class MinStack {
private:
stack<int> st;
struct Count
{
int min;
int count;
};
stack<Count> minSt;
public:
//因为在类和对象的知识,对于内置类型在构造函数中并不进行处理,对于其他数据类型会调用其本身的构造函数
//所以在这里既可以删除构造函数,也可以保留不做任何处理
MinStack() {
}
void push(int val) {
st.push(val);
if (minSt.empty())
{
Count cou;
cou.min = val;
cou.count = 1;
minSt.push(cou);
cout<<minSt.top().min<<minSt.top().count<<endl;
}
else
{
if (val < minSt.top().min)
{
Count cou1;
cou1.min = val;
cou1.count = 1;
minSt.push(cou1);
}
else if (val == minSt.top().min)
{
minSt.top().count++;
}
}
}
void pop() {
if (st.top() == minSt.top().min)
{
st.pop();
minSt.top().count--;
if(minSt.top().count==0)
minSt.pop();
}
else
{
st.pop();
}
}
int top()
{
return st.top();
}
int getMin()
{
return minSt.top().min;
}
};
力扣题二:栈的压入、弹出序列
https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&&tqId=11174&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking
解法:利用一个栈,来模拟实现出栈顺序。如果能和出栈的序列一一对应就说明是这样出栈是可行。先入栈序列中与出栈序列第一个不同的,入栈。当相同时,出栈跳到下一个元素,而该栈要pop该元素。再进行比较,如果入栈序列结束,直接就看栈和出栈序列接下来的元素是否可以一一对比。
class Solution {
public:
bool IsPopOrder(vector<int> pushV,vector<int> popV) {
stack<int> _st;
int i=0;
for(auto e:pushV)
{
_st.push(e);
while(!_st.empty()&&_st.top()==popV[i])
{
_st.pop();
i++;
}
}
return _st.empty();
}
};
力扣题三:逆波兰表达式求值
https://leetcode.cn/problems/evaluate-reverse-polish-notation/
说明:逆波兰表达式就是后缀表达式,按操作符的优先顺序,先写左操作数,再写右操作数,最后写操作符。在我们日常生活中的数学表达式是中缀表达式。
比如:1+2*3/4-5
写为后缀表达式就为 1 2 3 * 4 / + 5 -
解法:利用一个栈来存储操作数,当遍历输入的字符串到操作符的时候,选择栈顶的两个元素进行计算。记住先出栈的是右操作符,后出栈的是左操作符。
#include<string>
using namespace std;
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> _st;
for(auto str:tokens)
{
if(str=="+"||str=="-"||str=="*"||str=="/")
{
int right=_st.top();
_st.pop();
int left=_st.top();
_st.pop();
switch (str[0])
{case '+':
_st.push(left+right);
break;
case '-':
_st.push(left-right);
break;
case '*':
_st.push(left*right);
break;
case '/':
_st.push(left/right);
break;
}
}
else
{
//stoi函数功能是将n进制的字符串转换为十进制数字
_st.push(stoi(str));
}
}
return _st.top();
}
};
Queue
一.有关queue的介绍
- 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。
- 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。
- 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操
作:
empty:检测队列是否为空
size:返回队列中有效元素的个数
front:返回队头元素的引用
back:返回队尾元素的引用
push_back:在队列尾部入队列
pop_front:在队列头部出队列 - 标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标
准容器deque。
二.实现queue的代码
Queue.h的实现
#include<iostream>
#include<vector>
#include<list>
#include<deque>
using namespace std;
//队列先进先出的特性
namespace bit
{
template<class T, class Container = list<T>>
//在使用list做模板时,不能只引入#include<vector>,还要引入using namespace std;
class Queue
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
T top()
{
return _con.front();
}
private:
Container _con;
};
}
测试类的实现
#include<iostream>
#include"Queue.h"
int main()
{
bit::Queue<int> q;
q.push(1);
q.push(2);
q.push(3);
q.push(4);
q.push(5);
q.push(6);
while (!q.empty())
{
cout << q.top() << endl;
q.pop();
}
return 0;
}
三.有关queue的经典题目
用队列实现栈
https://leetcode.cn/problems/implement-stack-using-queues/
解法:用两个队列来解决。一个元素一个元素入栈,进栈的进行模拟。两个队列,一个队列是输入队列,一个是输出队列。当两个队列均为空,入栈的元素进入输入队列。当两个队列其一不为空,入栈的元素便进入不为空的队列。出栈的时候,将前size-1的元素挪到为空的队列,剩下的元素出队便为出栈。
代码如下:
class MyStack {
private:
queue q1;
queue q2;
public:
MyStack() {
}
//入栈
void push(int x) {
if (q1.empty() && q2.empty())
q1.push(x);
else if (q1.empty() && !q2.empty())
q2.push(x);
else if (!q1.empty() && q2.empty())
q1.push(x);
}
//出栈
int pop() {
int ret = 0;
if (q1.empty() && !q2.empty())
{
int i = q2.size() - 1;
while (i--)
{
int tmp = q2.front();
q2.pop();
q1.push(tmp);
}
ret = q2.back();
q2.pop();
}
else if (!q1.empty() && q2.empty())
{
int i = q1.size() - 1;
while (i--)
{
int tmp = q1.front();
q1.pop();
q2.push(tmp);
}
ret = q1.back();
q1.pop();
}
return ret;
}
int top() {
int ret = 0;
if (q1.empty() && !q2.empty())
ret = q2.back();
else if (!q1.empty() && q2.empty())
ret = q1.back();
return ret;
}
bool empty() {
return q1.empty() && q2.empty();
}
};
deque、vector、list
一. 细讲deque
1.特性
deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作(也就是可以进行头差头删、尾插尾删),且时间复杂度为O(1)。
2.底层实现示意图
3.使用
二.deque、vector、list的优缺点总结
deque的优点:1.可随机访问 2.支持头插头删
deque的缺点:1.随机访问较vector有一定消耗,不支持大量随机访问 2.中间插入删除要挪动数据,较list有一定的消耗
vector的优点:1.支持随机访问 2.CPU高速缓存命中高
vector的缺点:1.扩容消耗时间和空间 2.不支持头插头删以及中部的插入删除
list的优点:1.不需要扩容 2.支持头插头删以及中部的插入删除
list的缺点:1.不支持随机访问 2.CPU高速缓存命中低