一摞便当盒:最后放进去的,最先拿得到——"后进先出"。
想象你往桌上一摞便当盒里放东西:你只能往最顶上放(push),也只能从最顶上拿(pop)——想拿最底下那个?得先把上面的全搬走!这就是栈(stack,一摞)的核心规矩:后进先出(LIFO,Last In First Out)。弹夹也是:最后压进去的子弹最先打出去。栈只露出"顶"这一个口,每次操作只看栈顶,所以极快(O(1))。
规则只有一条:pop 永远弹出当前栈顶。跟着下面的操作,在纸上画便当盒,预测输出:
// 初始:栈空 push 1 push 2 push 3 pop // → 输出 ? pop // → 输出 ? push 4 pop // → 输出 ?
自己先推一遍,再对答案。
用数组存栈,只需要一个变量 top 记住"栈顶在第几格":
注意:栈底永远在 a[0],数组不用搬,每次只改 top 这个数字——所以 push、pop 都是 O(1),飞快。
// 数组实现栈(top = 栈顶元素的下标;top == -1 表示空) int a[100]; int top = -1; // 入栈:顶 +1,再放 void push(int x) { a[++top] = x; } // 出栈:拿走,顶 -1 int pop() { return a[top--]; } // 看栈顶(不拿走) int topVal() { return a[top]; } // 判空 bool empty() { return top == -1; }
手推一遍下面的操作,每步写下 top 和数组:
push 10 push 20 pop // 输出 ? topVal() // 得到 ?
看一段代码里的括号:(a + (b * c))——每个 ( 都要找到"自己那个" )。为什么正好用栈?因为最后打开的括号,必须最先关上——这就是 LIFO!
算法三步走:
(:压栈(记一笔"有个括号还没关")。):栈空 → 失败(没开的括号先关了);否则 弹一个(配上一对)。// 括号匹配 #include <iostream> using namespace std; bool match(string s) { int st[100], top = -1; for (int i = 0; i < s.size(); i++) { if (s[i] == '(') top++; // 压栈 else if (s[i] == ')') { if (top == -1) return false; // 没有可配的 top--; // 弹一个,配一对 } } return top == -1; // 最后必须全配完 }
((())) ② ()(())——各自成功还是失败?哪一步暴露的问题?((())):成功。( ( ( 三次压栈 top=2;) ) ) 三次弹栈 top 回到 -1,走完栈空 ✓。()(()):成功。( 压;) 弹(空);( 压;( 压;) 弹;) 弹——最后栈空 ✓。(() → 走完还剩 1 个未关的 ( → 失败("栈不空"暴露);
))(( → 第 1 个字符就遇到 ) 而栈空 → 失败("栈空还弹"暴露)。[、{,就得把左括号本身压进去,右括号来时比对是不是同一对——这就是"栈存什么"的设计题。平常我们写 3 + 4(运算符在中间)。如果把运算符挪到最后:3 4 + 就表示 3+4。这叫逆波兰式(后缀式)。妙处:不需要括号、不需要优先级——从左往右读,见到运算符,它前面的两个数就是它的操作数。
用栈算它,规则只有两条:
为什么弹的顺序是"先右后左"?因为减和除不满足交换律:栈顶是最后来的数,它正好是运算符右边的数。
手推一道:5 1 2 + 4 * + 3 -(它 = (5 + (1+2)×4) − 3)。
| 读到的 | 动作 | 栈(底→顶) |
|---|---|---|
| 5 | 压入 | [5] |
| 1 | 压入 | [5,1] |
| 2 | 压入 | [5,1,2] |
| + | 弹 2、1 → 1+2=3,压入 | [5,3] |
| 4 | 压入 | [5,3,4] |
| * | 弹 4、3 → 3×4=12,压入 | [5,12] |
| + | 弹 12、5 → 5+12=17,压入 | [17] |
| 3 | 压入 | [17,3] |
| - | 弹 3、17 → 17−3=14,压入 | [14] |
3 + 4 * 5(中缀)要处理优先级和括号,很麻烦。计算机常常先用栈把它翻译成逆波兰式 3 4 5 * +,再用栈求值——栈是表达式求值的发动机。// 输入一行逆波兰式(数字与运算符用空格分开,正整数) // 例如:5 1 2 + 4 * + 3 - → 输出 14 #include <iostream> #include <string> using namespace std; int main() { string s; getline(cin, s); int st[100], top = -1; int i = 0, n = s.size(); while (i < n) { while (i < n && s[i] == ' ') i++; // 跳过空格 if (i >= n) break; if (s[i] == '+' || s[i] == '-' || s[i] == '*' || s[i] == '/') { int b = st[top--]; // 先弹:右操作数 int a = st[top--]; // 后弹:左操作数 if (s[i] == '+') st[++top] = a + b; else if (s[i] == '-') st[++top] = a - b; else if (s[i] == '*') st[++top] = a * b; else st[++top] = a / b; i++; } else { int x = 0; // 读一个正整数 while (i < n && s[i] != ' ') x = x * 10 + (s[i] - '0'), i++; st[++top] = x; } } cout << st[top] << endl; // 最后栈顶就是答案 return 0; }
(() 括号匹配成功还是失败?是哪一步暴露的?( 压栈(top=0);第 2 个 ) 弹掉(top=-1);第 3 个 ( 压栈(top=0)。走完还有 1 个 ( 没关,"最后栈不空"这一步判出失败。8 2 / 3 +(= 8÷2+3),写出每步的栈和最终结果。