栈(Stack)是一种后进先出的线性表,只能在一端(栈顶)进出。
它和链表一样都是线性表,区别在于:链表可以随意插入删除,栈只能"从顶上"操作。


一、栈的概念

1.1 什么是栈

:一种 限定性 的线性表——只能在一端(栈顶) 进行插入和删除。

术语 含义
栈顶(top) 允许插入、删除的那一端
入栈(push) 在栈顶放入一个元素
出栈(pop) 从栈顶取走一个元素

1.2 核心规则:后进先出(LIFO)

LIFO(Last In First Out):最后放进去的,最先被取出来。

栈顶 ↑(只能从这端进出)  
┌───┐  
│ 3 │   ← 最后进,最先出  
│ 2 │  
│ 1 │   ← 最先进,最后出  
└───┘  

1 → 2 → 3 的顺序入栈,出栈顺序就变成 3 → 2 → 1(反过来)。

💡 口诀:栈是"后进先出",先进去的被压在底下,最后才能出来。

1.3 生活中的例子

例子 说明
一摞盘子 只能从最上面拿、最上面放
弹夹 后压入的子弹先射出
浏览器后退 最后访问的页面先返回
撤销操作 Ctrl+Z 撤销最后一步操作

二、数组模拟栈

2.1 为什么用数组模拟

C++ 的 STL 有现成的 stack,但竞赛里更常用数组模拟——更快、更好控制、方便配合单调栈等技巧。

2.2 栈的结构

用一个数组 q[] 存元素,一个变量 top 标记栈顶位置。

const int N = 100005;  
int q[N];     // 存栈里的元素  
int top = 0;  // 栈顶指针:top 就是元素个数,空栈时 top == 0  
栈(下标从 1 开始用):  
 q[1]  q[2]  q[3]  q[4]  ... ↓     ↓     ↓┌───┬───┬───┬───┬───┐  
│ 1 │ 2 │ 3 │   │   │  
└───┴───┴───┴───┴───┘  
 ↑ top = 3(指向栈顶元素 q[3])  

约定:top 指向栈顶元素,所以 top 的值就是元素个数;空栈时 top == 0

2.3 基本操作

q[++top] = x;    // 入栈:top 先加 1,再把 x 放进 q[top]  
q[top];          // 栈顶元素  
  
top--;           // 出栈:top 减 1,原栈顶就被"丢弃"了  
  
top == 0;        // 判空:top 为 0 就是空栈  
  
while (top) cout << q[top--] << " ";   // 清空栈(从栈顶到栈底输出)  

💡 记住这五句:

  • 入栈 q[++top] = x(先 ++ 再赋值)
  • 栈顶 q[top]
  • 出栈 top--
  • 元素数量 top
  • 空栈 top == 0
  • 清空 while (top) cout << q[top--] << " ";

2.4 用 STL 的 stack(对比)

#include <stack>  
stack<int> st;  
  
st.push(x);   // 入栈  
st.pop();     // 出栈(注意:不返回值)  
st.top();     // 取栈顶(不弹出)  
st.size();   // 元素数量 
st.empty();   // 判空  
st.size();    // 元素个数  

💡 竞赛里数组模拟栈更常用


三、栈解决的问题概括

栈"后进先出"的特性,天然适合下面几类问题:

场景 为什么用栈
括号匹配 左括号入栈,右括号和栈顶配对
表达式求值 存操作数,按优先级计算
函数调用 / 递归 系统底层用栈保存返回地址
回溯 / DFS 走不通时回退到上一步(栈顶)
单调栈 快速找每个元素左右第一个比它大/小的数

💡 一句话判断"该不该用栈":如果问题的处理顺序是"后进先出"(先处理最近发生的),就可以考虑栈。


四、前中后缀表达式 ——计算机如何解决数学算式问题

4.1 概念介绍

运算符的位置,表达式分三种:

名称 别称 形式 例子(中缀 a + b
前缀表达式 波兰式 运算符在 + a b
中缀表达式 普通写法 运算符在中间 a + b
后缀表达式 逆波兰式 运算符在 a b +
  • 中缀:我们习惯的写法,但要靠括号 + 优先级才能确定运算顺序,计算机不好直接算。
  • 前缀 / 后缀不需要括号,运算顺序由位置唯一确定,计算机用栈就能直接算。

4.2 栈求后缀表达式的值

规则(从左往右扫)

  1. 遇到操作数 → 入栈;
  2. 遇到运算符 → 弹出两个操作数,先弹的是右操作数,后弹的是左操作数,运算后把结果入栈;
  3. 扫完,栈里剩下的就是结果。

例:求 3 4 2 * + 5 -

读 3 入栈            栈:3  
读 4 入栈            栈:3 4  
读 2 入栈            栈:3 4 2  
读 * 弹 2、4        4 * 2 = 8 栈:3 8  
读 + 弹 8、3        3 + 8 = 11 栈:11  
读 5 入栈            栈:11 5  
读 - 弹 5、11       11 - 5 = 6 栈:6  

结果 = 6

⚠️ 减法和除法顺序要注意:a b - 表示 a - b。弹栈时先弹出来的是 b(右操作数),别算反了。

4.3 栈求前缀表达式的值

规则(从右往左扫)

  1. 遇到操作数 → 入栈;
  2. 遇到运算符 → 弹出两个操作数,先弹的是左操作数,后弹的是右操作数,运算后把结果入栈。

例:求 - + 3 * 4 2 5

读 5 入栈            栈:5  
读 2 入栈            栈:5 2  
读 4 入栈            栈:5 2 4  
读 * 弹 4、2        4 * 2 = 8 栈:5 8  
读 3 入栈            栈:5 8 3  
读 + 弹 3、8        3 + 8 = 11 栈:5 11  
读 - 弹 11、5       11 - 5 = 6 栈:6  

结果 = 6

💡 记牢两个方向:

  • 后缀:从左往右扫,先弹的是操作数;
  • 前缀:从右往左扫,先弹的是操作数。

4.4 中缀转前后缀:加括号法

(1)中缀 → 后缀

步骤

  1. 按优先级给中缀表达式加满括号
  2. 把每个运算符移到它对应的右括号外面
  3. 去掉所有括号。

例:a + b * c

① 加满括号: (a + (b * c))
② 运算符移到右括号后:  (a + (b * c))  →  a (b c * ) +
 ③ 去掉括号: a b c * +

结果:a b c * +

(2)中缀 → 前缀

步骤

  1. 加满括号;
  2. 运算符移到左括号外面
  3. 去掉括号。

例:a + b * c

① 加满括号: (a + (b * c))
② 运算符移到左括号前:  (a + (b * c))  →  + a * b c
 ③ 去掉括号: + a * b c

结果:+ a * b c

(3)综合例子

中缀:(a + b) * c - d

加满括号: (((a + b) * c) - d)  
转后缀(运算符移右括号后): a b + c * d -  
转前缀(运算符移左括号前): - * + a b c d

💡 加括号法核心:先按优先级加满括号,再把运算符挪到括号外,最后去括号。后缀挪到"右括号后",前缀挪到"左括号前"。

4.5 前后缀转回中缀

转中缀是"加括号法"的逆过程:看到运算符,就把它的两个操作数用括号括起来。笔试手算即可,不需要栈。

(1)后缀 → 中缀

从左往右看,遇到运算符,把它和前面两个操作数括起来。

例:a b + c * d -

① 扫到 + ,前面两个是 a、b → 括成 (a + b)  式子变成:(a+b) c * d -  
  
② 扫到 * ,前面两个是 (a+b)、c → 括成 ((a+b) * c)  式子变成:((a+b)*c) d -  
  
③ 扫到 - ,前面两个是 ((a+b)*c)、d → 括成 (((a+b)*c) - d)  
结果:(a + b) * c - d  

(2)前缀 → 中缀

从右往左看,遇到运算符,把它和后面两个操作数括起来。

例:- * + a b c d

① 从右往左,扫到 + ,后面两个是 a、b → (a + b)  
  式子变成:- * (a+b) c d  
  
② 扫到 * ,后面两个是 (a+b)、c → ((a+b) * c)  
  式子变成:- ((a+b)*c) d  
  
③ 扫到 - ,后面两个是 ((a+b)*c)、d → (((a+b)*c) - d)  
  
结果:(a + b) * c - d