4.7 KiB
4.7 KiB
C++ 算法讲义:栈 (Stack)
1. 引言
- 问题引入: 想象你在餐厅洗盘子。客人们不断把用过的盘子堆在桌上,你每次洗的时候,是从这叠盘子的最底端抽,还是从最顶端拿?显然,你只能拿走最后放上去的那个。
- 价值: 这种“后来者居上”的逻辑在计算机世界无处不在:浏览器的“后退”按钮、编辑器的“撤销”功能 (Ctrl+Z)、甚至复杂的表达式求值和函数调用,背后都是栈在支撑。它是掌握高级数据结构(如树、图)的敲门砖。
- 学习目标:
理解栈的 LIFO (后进先出) 原理,学会手动模拟栈以及使用 C++ STL 标准库中的
stack。
2. 核心思想
- 栈 (Stack) 是一种遵循“后进先出”(Last In, First Out,简称 LIFO)原则的线性数据结构。
- 入栈 (Push):把新元素放到最顶端。
- 出栈 (Pop):把最顶端的元素移走。
- 栈顶 (Top):当前最后进入、最上面的那个元素。
3. 代码实现
方法一:手动数组模拟
使用数组 S[] 存数据,用变量 top_ptr 指向栈顶下标(初始为 -1)。
int S[100];
int top_ptr = -1; // 初始为空
void push(int x) {
top_ptr++; // 腾出地方
S[top_ptr] = x; // 放下盘子
}
void pop() {
if (top_ptr != -1) top_ptr--; // 逻辑删除:指针下移,下次push会覆盖旧数据
}
int top() {
return S[top_ptr]; // 返回当前的“顶端”
}
方法二:使用 C++ STL 标准库 (推荐用法)
实际开发中,我们直接调用 #include <stack>。
#include <iostream>
#include <stack> //栈的头文件
using namespace std;
int main() {
stack<int> s; // 定义一个整数栈
s.push(10); // 入栈
s.push(20);
if (!s.empty()) { // 访问前务必检查是否为空!
cout << "栈顶元素: " << s.top() << endl; // 输出 20
}
s.pop(); // 弹出 20
return 0;
}
4. 经典应用
A. 括号匹配 (例 15-5)
括号匹配的本质是最近匹配原则:最后打开的左括号,必须最先被闭合。
核心逻辑拆解
我们可以把栈想象成一个“待完成任务清单”:
- 遇到左括号
([{:- 操作:
s.push(ch)。 - 含义:你许下一个承诺,后面必须有一个对应的“盖子”来找它。
- 操作:
- 遇到右括号
)]}:- 第一步:检查是否有盒子。 如果此时栈是空的(
s.empty()),说明你手里拿着盖子却没盒子,直接判定 非法。 - 第二步:检查类型。 看一眼栈顶
s.top()。如果我手里是],栈顶必须是[。- 匹配:
s.pop()。说明这个盒子成功盖好了,从清单中划掉。 - 不匹配:比如
( ],直接判定 非法。
- 匹配:
- 第一步:检查是否有盒子。 如果此时栈是空的(
当整个字符串读完后,必须检查栈是否为空:
- 如果
s.empty()为真,说明所有承诺都兑现了\rightarrow成功! - 如果栈里还剩东西,说明有的括号“开了口却没盖上”
\rightarrow非法。
B. 后缀表达式 (例 15-6)
后缀表达式(又称逆波兰表达式)最大的魅力在于:它不需要括号,也不需要考虑加减乘除的优先级,因为位置已经决定了一切。
步骤:
假设我们要计算:3 4 2 * + 5 -
- 扫描到数字:
- 直接
s.push(num)。 - 注意:在代码实现时,如果输入是
123.这种带点的格式,记得用循环把'1','2','3'拼接成整数123再入栈。
- 直接
- 扫描到运算符(如
+,-,\*,/):- 关键动作:从栈中弹出两个数。
- 注意顺序:
- 第一个弹出的是右操作数
b = s.top();s.pop(); - 第二个弹出的是左操作数
a = s.top();s.pop();
- 第一个弹出的是右操作数
- 计算并将结果压回:计算
a \text{ 运算符 } b的结果,然后s.push(result)。
- 扫描到终止符
@:- 此时栈顶剩下的唯一一个数字,就是最终答案。
为什么第一个弹出的是右操作数?
看这个例子:10 2 / (即 10 \div 2)
- 栈里先入 $10$,后入 $2$。
- 弹出第一个数是 $2$(右边),第二个数是 $10$(左边)。
- 计算 $10 / 2 = 5$。如果顺序反了,变成
2 / 10就全错了!
5. 总结
- 要点回顾:
- 栈是 LIFO(后进先出)。
stack的常用操作:push(入),pop(出),top(看栈顶),empty(判空)。- 注意:
pop()和top()前一定要判断栈是否为空,否则程序会崩溃。
