一、栈(Stack):先进后出的"叠盘子"
栈的核心特点是先进后出(LIFO,Last In First Out),就像叠盘子一样:最后放上去的盘子,要先拿下来;最底下的盘子,只能等上面的都拿完才能取。

image.png
1. 栈的定义方式
栈有两种常见定义方式,核心是指定存储的数据类型和底层容器(默认用deque)。
| 定义方式 | 代码示例 | 说明 |
|---|---|---|
| 普通定义(默认底层容器) |
| 存储int类型,底层默认用deque容器 |
| 指定底层容器 |
| 分别指定vector、list作为底层容器 |
注意:如果不指定底层容器,C++标准库会默认用deque——因为deque兼顾了vector的随机访问和list的高效插入删除,适合栈的操作场景。
2. 栈的常用操作
栈的操作很简洁,核心围绕"栈顶"(毕竟只能从栈顶进出),常用成员函数如下:
| 成员函数 | 功能 | 示例 |
|---|---|---|
|
| 判断栈是否为空,返回bool值 |
|
|
| 获取栈中有效元素的个数,返回整数 |
|
|
| 获取栈顶元素(不删除),注意栈空时调用会报错 |
|
|
| 将元素x压入栈顶 |
|
|
| 删除栈顶元素(不返回),栈空时调用会报错 |
|
|
| 交换两个栈的所有元素 |
|
3. 栈的使用示例
下面代码演示了栈的基本操作:压入4个元素,然后从栈顶依次弹出并打印。
代码语言:cpp
AI代码解释
#include <iostream>
#include <stack> // 包含stack头文件
#include <vector> // 若指定vector为底层容器,需包含此头文件
using namespace std;
int main() {
// 定义一个以vector为底层容器的栈,存储int类型
stack<int, vector<int>> st;
// 压入元素(栈顶依次变成1→2→3→4)
st.push(1);
st.push(2);
st.push(3);
st.push(4);
// 打印栈的大小(此时有4个元素)
cout << "栈的大小:" << st.size() << endl; // 输出:4
// 循环弹出栈顶元素(直到栈空)
while (!st.empty()) {
// 先获取栈顶元素并打印
cout << st.top() << " ";
// 再删除栈顶元素(注意:pop()不返回元素,必须先top()再pop())
st.pop();
}
cout << endl; // 输出:4 3 2 1(符合先进后出)
return 0;
}
二、队列(Queue):先进先出的"排队"
队列的核心特点是先进先出(FIFO,First In First Out),就像日常生活中排队买东西:先排队的人先结账,后排队的人只能等前面的人结束才能轮到。

image.png
1. 队列的定义方式
和栈类似,队列也需要指定数据类型和底层容器(默认同样用deque)。
| 定义方式 | 代码示例 | 说明 |
|---|---|---|
| 普通定义(默认底层容器) |
| 存储int类型,底层默认用deque |
| 指定底层容器 |
| 分别指定vector、list作为底层容器 |
注意:虽然可以用vector作为底层容器,但队列的pop()操作需要删除队头元素——vector删除队头元素效率很低(要移动所有元素),所以实际中更推荐用list或默认的deque。
2. 队列的常用操作
队列的操作围绕"队头"(出队)和"队尾"(入队),常用成员函数如下:
| 成员函数 | 功能 | 示例 |
|---|---|---|
|
| 判断队列是否为空,返回bool值 |
|
|
| 获取队列中有效元素的个数 |
|
|
| 获取队头元素(不删除),队列空时调用报错 |
|
|
| 获取队尾元素(不删除),队列空时调用报错 |
|
|
| 将元素x插入队尾 |
|
|
| 删除队头元素(不返回),队列空时调用报错 |
|
|
| 交换两个队列的所有元素 |
|
3. 队列的使用示例
下面代码演示了队列的基本操作:插入4个元素,然后从队头依次弹出并打印。
代码语言:cpp
AI代码解释
#include <iostream>
#include <queue> // 包含queue头文件
#include <list> // 若指定list为底层容器,需包含此头文件
using namespace std;
int main() {
// 定义一个以list为底层容器的队列,存储int类型
queue<int, list<int>> q;
// 插入元素(队尾依次加入1→2→3→4,队头始终是1)
q.push(1);
q.push(2);
q.push(3);
q.push(4);
// 打印队列的大小(此时有4个元素)
cout << "队列的大小:" << q.size() << endl; // 输出:4
// 循环弹出队头元素(直到队空)
while (!q.empty()) {
// 先获取队头元素并打印
cout << q.front() << " ";
// 再删除队头元素
q.pop();
}
cout << endl; // 输出:1 2 3 4(符合先进先出)
return 0;
}
三、实战:用栈解决逆波兰表达式求值
栈的一个经典应用是计算逆波兰表达式(也叫后缀表达式)。先搞懂什么是中缀、后缀表达式:
1. 中缀表达式 vs 后缀表达式
- 中缀表达式:我们平时写的
1 + 2 * 3,操作符在两个操作数中间,需要考虑优先级(先乘后加); - 后缀表达式:把操作符放在两个操作数后面,比如
1 2 3 * +,不需要考虑优先级,按顺序计算即可。

image.png
后缀表达式的计算规则很简单:遇到操作数就入栈,遇到操作符就弹出栈顶两个元素,计算后将结果再入栈,最终栈里剩下的就是结果。
比如计算1 2 3 * +:
- 1、2、3依次入栈 → 栈:1, 2, 3;
- 遇到
*,弹出3(右操作数)和2(左操作数),计算2*3=6,6入栈 → 栈:1, 6; - 遇到
+,弹出6和1,计算1+6=7,7入栈 → 栈:7; - 最终结果就是7。

image.png
2. 力扣例题:逆波兰表达式求值
题目链接:150. 逆波兰表达式求值 - 力扣(LeetCode)
题目描述
给你一个字符串数组tokens,表示一个逆波兰表达式,返回该表达式的计算结果。操作符只有+、-、*、/,且所有操作数都是整数。
解题思路
完全遵循后缀表达式的计算规则,用栈实现:
- 遍历字符串数组
tokens; - 若当前字符串是操作数(不是
+、-、*、/),转成整数后入栈; - 若当前字符串是操作符,弹出栈顶两个元素(注意:先弹的是右操作数,后弹的是左操作数);
- 计算两个元素的结果,将结果入栈;
- 遍历结束后,栈中仅剩的一个元素就是最终结果。

image.png
代码实现
代码语言:cpp
AI代码解释
#include <iostream>
#include <vector>
#include <stack>
#include <string>
using namespace std;
class Solution {
public:
int evalRPN(vector<string>& tokens) {
// 1. 定义一个栈,存储操作数和中间结果
stack<int> st;
// 2. 遍历字符串数组
for (auto& str : tokens) {
// 2.1 判断当前字符串是否为操作符
if (str == "+" || str == "-" || str == "*" || str == "/") {
// 弹出栈顶两个元素(注意顺序:先弹右操作数,后弹左操作数)
int right = st.top(); // 右操作数(后入栈的)
st.pop();
int left = st.top(); // 左操作数(先入栈的)
st.pop();
// 根据操作符计算结果,结果入栈
switch (str[0]) { // str是单个字符的字符串,取第一个字符判断
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 {
// 2.2 是操作数,转成整数后入栈(stoi:string to int)
st.push(stoi(str));
}
}
// 3. 遍历结束,栈中仅剩的元素就是结果
return st.top();
}
};
// 测试代码
int main() {
Solution sol;
// 示例:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
vector<string> tokens = {"10","6","9","3","+","-11","*","/","*","17","+","5","+"};
cout << "逆波兰表达式结果:" << sol.evalRPN(tokens) << endl; // 输出:22
return 0;
}
四、底层原理:为什么栈和队列是"容器适配器"?
前面我们反复提到"容器适配器",到底什么是适配器?
简单说:适配器是一种"包装器",它基于已有的容器(如vector、list、deque),通过限制部分操作、封装特定接口,实现新的数据结构功能。
栈和队列本身不存储数据,而是"借用"底层容器的存储空间,只暴露符合自身逻辑的接口(比如栈只暴露栈顶操作,队列只暴露队头/队尾操作)。
4910

被折叠的 条评论
为什么被折叠?



