文章目录
- stack&queue
- 1. 介绍
- 1.1 stack
- 1.2 queue
 
- 2. 接口
- 2.1 stack
- 2.2 queue
 
- 3. OJ
- 3.1 最小栈
- 3.2 验证栈序列
- 3.3 逆波兰表达式求值
- 3.4 用栈实现队列
- 3.5 用队列实现栈
 
- 4. 模拟实现
- 4.1 stack
- 4.2 queue
 
 
stack&queue
1. 介绍
1.1 stack
栈(Stack)是一种常见的数据结构,它是一种“后进先出”(Last In First Out,LIFO)的数据结构。栈可以看做是一种特殊的线性表,只能在栈顶进行插入和删除操作。栈顶是允许操作的,而栈底是固定的。
栈的插入被称为压栈、进栈、入栈,删除被称为出栈、弹栈。
后进先出,先进后出, L I F O LIFO LIFO 原则(Last In First Out)。

1.2 queue
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表,是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。
入队列就是队尾插入数据,出队列就是队头删除数据。
先进先出,后进后出,即 F I F O FIFO FIFO 原则(First In First Out)。

2. 接口
2.1 stack
STL中的栈和队列不是容器而是容器适配器。
template <class T, class Container = deque<T> > 
class stack;
stack 的底层可以使用任何容器,只要该容器支持empty、back、push_back、pop_back这些接口。如果没有为stack指定底层容器,默认使用deque。
| 接口声明 | 解释 | 
|---|---|
| explicit stack (const container& ctnr = container()) | 构造函数 | 
| bool empty() const | 判空 | 
| size_type size() const | 元素个数 | 
| value_type& top() | 栈顶元素 | 
| void push (const value_type& val) | 尾插 | 
| void pop() | 尾删 | 
| bool operator== (const stack<T,Ctnr>& lhs, const stack<T,Ctnr>& rhs) | 关系运算 | 
2.2 queue
template <class T, class Container = deque<T> >
class queue;
queue也是容器适配器。底层容器要求和stack一样。
| 接口声明 | 解释 | 
|---|---|
| explicit queue (const container& ctnr = container()) | 构造函数 | 
| bool empty() const | 判空 | 
| size_type size() const | 元素个数 | 
| value_type& front() | 队头元素 | 
| value_type& back() | 队尾元素 | 
| void push (const value_type& val) | 尾插 | 
| void pop() | 头删 | 
| bool operator== (const stack<T,Ctnr>& lhs, const stack<T,Ctnr>& rhs) | 关系运算 | 
3. OJ
3.1 最小栈
最小栈
class MinStack {
public:
    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;
};
准备两个栈,一个用来正常入栈出栈,一个用来存储最小值。
- 当入栈元素大于当前最小值时,只入到普通栈,不入最小栈。
- 当入栈元素小于等于当前最小值时,将其入到普通栈,也入到最小栈中。
出栈时,判断栈顶元素是否和最小值相等,相等则把最小栈中的元素也弹出。

3.2 验证栈序列
验证栈序列 / 剑指 Offer 31. 栈的压入、弹出序列
一个入栈序列对应多种出栈序列,只能拿一个栈用来模拟,如果能匹配出当前的出栈序列,则两者是匹配的。
class Solution {
public:
    bool IsPopOrder(vector<int> pushV, vector<int> popV) {
    	stack<int> st; // 模拟栈
        int popi = 0;  // 出数组指针
        for (auto& e : pushV) {
            st.push(e); // 入栈数组只管向栈中入元素
            while (!st.empty() && st.top() == popV[popi]) { // 出栈数组和模拟栈进行比较
                st.pop();
                ++popi;
            }
        }
        return st.empty(); // 出栈数组遍历结束或栈为空
    }
};
定义一个模拟栈,定义两个指针指向出入数组的起始位置,向后遍历。
入栈数组只管向栈中入元素,只有出栈数组和模拟栈进行比较:当栈顶元素和出栈指针所指元素相等时,将栈顶元素出栈并++出栈指针。
成功示例图示

不成功示例

待入栈数组遍历结束后,若出栈数组遍历结束或栈为空,说明匹配成功,若栈中仍有元素或出栈数组未遍历结束,说明匹配不成功。
3.3 逆波兰表达式求值
逆波兰表达式求值
中缀表达式转后缀表达式的目的是,将操作符按照运算顺序从左到右依次排好,方便计算机进行运算。
中缀转后缀
遍历中缀字符串:
- 遇到操作数,直接输出。
- 遇到操作符,如果是空栈,直接入栈;如果栈非空,将其与栈顶比较优先级;
- 优先级比栈顶元素高,则入栈,比栈顶低或相等,栈顶出栈并输出。
- 遍历结束后,将栈中元素全部输出。
后缀运算
遍历后缀表达式:
- 遇到操作数,直接入栈。
- 遇到操作符,连续取两个栈顶元素(先出为右,后出为左)作操作数与其运算,运算结果入栈。
- 遍历结束后,栈顶即结果。
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
    	stack<int> st;
        for (auto& s : tokens) {
            if (s == "+" || s == "-" || s == "*" || s == "/") {
                string optor(s); //得操作符
                int op2 = st.top(); //先出为右操作数
                st.pop();
                int op1 = st.top(); //后出为左操作数
                st.pop();
                switch(s[0]) {
                case '+':
                    st.push(op1 + op2);//将运算结果入栈
                    break;
                case '-':
                    st.push(op1 - op2);
                    break;                   
                case '*':
                    st.push(op1 * op2);
                    break;
                case '/':
                    st.push(op1 / op2);
                    break;
                }
            }
            else {
                st.push(stoi(s)); // 操作数入栈,待遍历得操作符与之运算
            }
        }
        return st.top();//返回栈顶元素
    }
};
3.4 用栈实现队列
用栈实现队列
使用两个栈,一个用来入,一个用来出。
只要出栈为空,就将入栈中的数据全部导入出栈。只有当出栈为空时,才会导入,以免打乱顺序。保证了出栈和取数据时有元素。
class MyQueue {
public:
    void push(int x) {
        _pushST.push(x);
    }
    
    int pop() {
        int tmp = peek(); //返回队头数据的同时进行了转移判断
        _popST.pop();
        return tmp;
    }
    
 	int peek() {
		if (_popST.empty()) { // 只需要在取数据处判断是否需要转移数据
			while (!_pushST.empty()) {
				_popST.push(_pushST.top());
				_pushST.pop();
			}
		}
		return _popST.top();
	} 
    
    bool empty() {
        return _popST.empty() && _pushST.empty();
    }
private:
    stack<int> _pushST;
    stack<int> _popST;
};

入栈pushST和出栈popST互不影响,分别完成入队的出队的任务。只要popST为空,就将pushST中元素移入即可。
3.5 用队列实现栈
用队列实现栈
用队列实现栈,需要考虑栈是先进后出的结构,都是顺序容器插入操作一致,删除操作需要将队列中的前n-1个元素移入另一个队列,只留最后一个元素。
class MyStack {
public:
    void push(int x) {
        q1.push(x);
    }
    int pop() {
        int tmp = top();
        q1.pop();
        swap(q1, q2);//pop之后交换,把q2变成q1,相当于栈出了栈顶
        return tmp;
    }
    int top() {
        while(q1.size() > 1) {
            q2.push(q1.front());
            q1.pop();
        }
        int tmp = q1.front();
        return tmp;
    }
    bool empty() {
        return q1.empty() && q2.empty();
    }
private:
    queue<int> q1;
    queue<int> q2;
};
4. 模拟实现
4.1 stack
适配器是一种设计模式。数据结构栈可以用数组和链表实现,C++中栈被实现成容器的适配器,通过复用底层容器的接口。
template <class T> class stack {
    void push(const T& x) {
        _v.push_back(x);
    }
    //...
private:
    vector<T> _v;
}
直接将容器 vector 作成员变量,所有接口都调用 vector 的接口即可。
template <class T, class Container = deque<T>>
class stack {
public:
    void push(int x) {
        _con.push_back(x);
    }
    void pop() {
        _con.pop_back();
    }
    bool empty() const {
        return _con.empty();
    }
    bool size() const {
        return _con.size();
    }
    T& top() const { 
        return _con.back();
    }
private:
    Container _con;
};
STL直接将容器类型作为类模板参数传入,支持自定义底层容器,并采用缺省参数的形式,指定默认容器为 deque。
4.2 queue
template<class T, class Container = deque<T>>
class queue
{
public:
    void push(const T& x) {
        _con.push_back(x);
    }
    void pop() {
        _con.pop_front();
    }
    T& front() {
        return _con.front();
    }
    T& back() {
        return _con.back();
    }
    size_t size() {
        return _con.size();
    }
    bool empty() {
        return _con.empty();
    }
private:
    Container _con;
};
queue也是容器适配器,只要按照queue的特性,将适配容器的接口换一下即可。



















