栈和队列的应用

这一节的产出密度在全书排得上号:5 个小节,挂了 8 条命题追踪,其中中缀转后缀在 2012、2014、2024 三年考过,栈在函数调用中的作用在 2015、2017 考过,队列在 2009、2016 考过。

结构上,3.3 只有一条主线:「等待中的东西越晚来的越急」就用栈,「排队的东西先到先服务」就用队列。括号匹配是这条主线最干净的例子——王道用「期待」两个字讲了一整段;表达式求值是它最值钱的例子;递归则是它在系统层面的实现。

需要提前说清的是 3.3.2 有两套手算方法:画表法(模拟运算符栈,教材表 3.1 用的)和加括号法(三步手算,教材正文先给的)。两套都要会——问「转换结果」用加括号法快,问「某一时刻栈里有什么」只能用画表法。

机制

3.3.1 栈在括号匹配中的应用

设表达式中允许包含圆括号和方括号,嵌套的顺序任意:([]()) 或 [([][])] 均为正确格式,[(]) 或 ([() 或 (()] 均为不正确格式。

教材用「期待」把栈讲透了。考虑 [ ( [ ] [ ] ) ](编号 1~8):

  1. 计算机接收第 1 个括号 [ 后,期待与之匹配的第 8 个括号 ] 出现。
  2. 获得了第 2 个括号 (,此时第 1 个括号 [ 暂时放在一边,而急迫期待与之匹配的第 7 个括号 ) 出现。
  3. 获得了第 3 个括号 [,此时第 2 个括号 ( 暂时放在一边,而急迫期待与之匹配的第 4 个括号 ] 出现。第 3 个括号的期待得到满足,消解之后,第 2 个括号的期待匹配又成为当前最急迫的任务。
  4. 以此类推,可见该处理过程与栈的思想吻合。

算法:

  1. 初始设置一个空栈,顺序读入括号。
  2. 若是左括号,则作为一个新的更急迫的期待压入栈中,自然使原有的栈中所有未消解的期待的急迫性降了一级。
  3. 若是右括号,则或使置于栈顶的最急迫期待得以消解,或是不合法的情况(括号序列不匹配,退出程序)。

算法结束时,栈为空,否则括号序列不匹配。

3.3.2 三种表达式与表达式树

形式例特点
中缀3+4操作符以中缀形式处于操作数的中间;括号是必需的
前缀+3 4又称波兰式
后缀3 4+又称逆波兰式;没有括号,只有操作数和运算符,运算符在操作数后面

中缀表达式不容易被计算机解析,但仍被许多程序语言使用,因为它更符合人们的思维习惯。与前缀或后缀表达式不同的是,中缀表达式中的括号是必需的:计算过程中必须用括号将操作符和对应的操作数括起来,用于指示运算的次序。后缀表达式中考虑了运算符的优先级,因此不需要括号。

中缀表达式 A+B*(C-D)-E/F 对应的后缀表达式为 ABCD-*+EF/-。把它与表达式树(教材图 3.15)的后序遍历序列比较,可发现它们有异曲同工之妙:

flowchart TD
    R["−"] --- P["+"]
    R --- D1["/"]
    P --- A["A"]
    P --- M["*"]
    D1 --- E["E"]
    D1 --- F["F"]
    M --- B["B"]
    M --- S["−"]
    S --- C["C"]
    S --- D["D"]

后序遍历 = 后缀表达式,先序遍历 = 前缀表达式,中序遍历 = 中缀表达式(不含括号)。 王道在 3.3.7 第 2 题的解析里直接建议:「学完第 5 章后,可将表达式画成二叉树的形式,再用后序遍历即可求得后缀表达式。」 2024 真题(第 19 题)的标准解法就是先画树再后序遍历。

3.3.2 中缀转后缀:加括号法(手算首选)

教材给出的三步手算法:

  1. 按照运算符的运算顺序对所有运算单位加括号。
  2. 将运算符移至对应括号的后面,相当于按「左操作数 右操作数 运算符」重新组合。
  3. 去除所有括号。

以 A+B*(C-D)-E/F 为例(下标表示运算符的运算顺序):

步结果
① 加括号((A+₃(B*₂(C-₁D)))-₅(E/₄F))
② 运算符后移((A(B(CD)-₁)*₂)+₃(EF)/₄)-₅
③ 去括号ABCD-₁*₂+₃EF/₄-₅

这个方法最容易错的一步是①:加括号必须严格按运算顺序(先乘除后加减、同级从左到右、括号内优先),少括一层结果就整个错位。

3.3.2 中缀转后缀:栈法(画表法)

在计算机中,中缀转后缀时需要借助一个栈,用于保存暂时还不能确定运算顺序的运算符。从左到右依次扫描中缀表达式中的每一项:

  1. 遇到操作数:直接加入后缀表达式。
  2. 遇到界限符:若为 (,则直接入栈;若为 ),则不入栈,且依次弹出栈中的运算符并加入后缀表达式,直到遇到 ( 为止,并直接删除 (。
  3. 遇到运算符:
    • ① 若其优先级高于栈顶运算符,或遇到栈顶为 (,则直接入栈;
    • ② 若其优先级低于或等于栈顶运算符,则依次弹出栈中的运算符并加入后缀表达式,直到遇到一个优先级低于它的运算符,或遇到 (,或栈空为止,之后将当前运算符入栈。

按上述方法扫描所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。

A+B*(C-D)-E/F 的 14 步(教材表 3.1):

步待处理序列栈内后缀表达式扫描项说明
1A+B*(C-D)-E/FAA 加入后缀表达式
2+B*(C-D)-E/FA++ 入栈
3B*(C-D)-E/F+ABB 加入后缀表达式
4*(C-D)-E/F+AB** 优先级高于栈顶,* 入栈
5(C-D)-E/F+*AB(( 直接入栈
6C-D)-E/F+*(ABCC 加入后缀表达式
7-D)-E/F+*(ABC-栈顶为 (,− 直接入栈
8D)-E/F+*(-ABCDD 加入后缀表达式
9)-E/F+*(-ABCD)遇到 ),弹出 −,删除 (
10-E/F+*ABCD--− 优先级低于栈顶,依次弹出 *、+,− 入栈
11E/F-ABCD-*+EE 加入后缀表达式
12/F-ABCD-*+E// 优先级高于栈顶,/ 入栈
13F-/ABCD-*+EFF 加入后缀表达式
14-/ABCD-*+EF字符扫描完毕,弹出剩余运算符
—ABCD-*+EF/-结束

3.3.2 后缀表达式求值

从左往右依次扫描表达式的每一项:

  • 若该项是操作数,则将其压入栈中;
  • 若该项是操作符 <op>,则从栈中退出两个操作数 和 ,形成运算指令 ,并将计算结果压入栈中。

当所有项都扫描并处理完后,栈顶存放的就是最后的计算结果。

求值需 12 步(教材表 3.2):

步扫描项类型动作栈中内容
1置空栈空
2~5A B C D操作数依次入栈A B C D
6-操作符 出栈,计算 ,结果 入栈A B R₁
7*操作符 出栈,计算 ,结果 入栈A R₂
8+操作符 出栈,计算 ,结果 入栈R₃
9~10E F操作数依次入栈R₃ E F
11/操作符 出栈,计算 ,结果 入栈R₃ R₄
12-操作符 出栈,计算 ,结果 入栈R₅

先退栈的是右操作数,后退栈的才是左操作数。 对 +、* 无所谓,对 -、/ 写反就整道题错——2018 真题正是拿这一点出的题(题干把这条规则明写成「执行 b op a」)。

3.3.2 栈的深度:两种栈、两种问法

所谓栈的深度,是指栈中的元素个数,通常是给出入栈和出栈序列,求最大深度(栈的容量应大于或等于最大深度)。王道提醒:有时会间接给出入栈和出栈序列,例如以中缀表达式和后缀表达式的形式给出。

表达式题里有两个不同的栈,问的是哪一个要看清:

栈什么时候问怎么数
运算符栈中缀转后缀时(2012、2014)按栈法画表,界限符 ( 也占位置
运算数栈后缀表达式求值时(3.3.6 第 4 题)按求值表画,已算出的中间结果 也占位置

王道给运算数栈深度提供了一个更快的技巧:根据运算符优先级,统计已依次入栈但还未参与计算的运算符的个数。

3.3.3 栈在递归中的应用

递归:若在一个函数、过程或数据结构的定义中又应用了它自身,则称为递归定义。递归通常把一个大型的复杂问题层层转化为一个与原问题相似的规模较小的问题来求解,大大减少了程序的代码量,但通常情况下它的效率并不是太高。

以斐波那契数列为例:

递归模型不能是循环定义的,必须满足下面两个条件:

  • 递归表达式(递归体)
  • 边界条件(递归出口)

递归的精髓在于能否将原始问题转换为属性相同但规模较小的问题。

在递归调用的过程中,系统为每一层的返回点、局部变量、传入实参等开辟了递归工作栈来进行数据存储,递归次数过多容易造成栈溢出。而其效率不高的原因是递归调用过程中包含很多重复的计算:以 为例(教材图 3.16), 被计算 2 次, 被计算 3 次, 被调用 5 次, 被调用 3 次。

可以将递归算法转换为非递归算法,通常需要借助栈来实现这种转换——但**「借助栈」不等于「必须用栈」**:单向递归和尾递归可以直接用迭代消除,这是 2017 真题的第一个错项。

数递归调用次数时,画递归调用树是唯一可靠的办法,而且它还顺带回答另一类问题:某个函数的执行次序等于其在递归调用树的先序遍历中的次序。

3.3.4 队列在层次遍历中的应用

在信息处理中有一大类问题需要逐层或逐行处理。这类问题的解决方法往往是在处理当前层或当前行时就对下一层或下一行做预处理,把处理顺序安排好,等到当前层或当前行处理完毕,就可以处理下一层或下一行。使用队列是为了保存下一步的处理顺序。

对二叉树(教材图 3.17,层序为 ;;;):

序说明队内队外
1 入
2 出, 入
3 出, 入
4 出, 入
5 出, 入
6 出, 入
7 出
8 出

过程:① 根结点入队。② 若队空(所有结点都已处理完毕),则结束遍历;否则重复③。③ 队列中第一个结点出队,并访问之。若其有左孩子,则将左孩子入队;若其有右孩子,则将右孩子入队,返回②。

图的广度优先搜索类似于树的层序遍历,都要借助于队列——见 6.3 图的遍历。

3.3.5 队列在计算机系统中的应用

队列在计算机系统中的应用非常广泛,教材从两个方面阐述:

① 解决主机与外部设备之间速度不匹配的问题。 以主机和打印机之间速度不匹配为例:主机输出数据的速度比打印数据的速度要快得多,因为速度不匹配,若直接把输出的数据送给打印机打印,则显然是不行的。解决的方法是设置一个打印数据缓冲区,主机把要打印输出的数据依次写入这个缓冲区,写满后就暂停输出,转去做其他的事情。打印机则从缓冲区中按照先进先出的原则依次取出数据并打印,打印完后再向主机发出请求。由此可见,打印数据缓冲区中所存储的数据就是一个队列。

② 解决由多用户引起的资源竞争问题。 CPU 资源的竞争就是一个典型的例子。操作系统通常按照每个请求在时间上的先后顺序,把它们排成一个队列,每次把 CPU 分配给队首请求的用户使用。当相应的程序运行结束或用完规定的时间间隔后,令其出队,再把 CPU 分配给新的队首请求的用户使用。

手算模板

中缀转后缀(问结果 → 加括号法)

  1. 按运算顺序逐层加括号,每个二元运算单位都括起来。
  2. 每个运算符移到它自己那层括号的右边。
  3. 去掉全部括号。
  4. 校验:后缀式中操作数的相对顺序必须与中缀式完全一致(转换过程中操作数直接输出,顺序固定不变)。

中缀转后缀(问某时刻栈内容 → 画表法)

  1. 列五栏表:待处理序列 / 栈内 / 后缀表达式 / 扫描项 / 说明。
  2. 操作数直接进后缀式;( 直接入栈;) 弹到 ( 为止并删 (。
  3. 运算符:高于栈顶或栈顶是 ( 就入栈;低于或等于栈顶就先弹再入。
  4. 扫描完把栈里剩下的全部弹出。

中缀转后缀(问某时刻栈内容 → 王道的反推法,比画表快)

  1. 先用加括号法求出完整的后缀式。
  2. 看目标操作数在后缀式中的位置,它后面的运算符要么还没入栈、要么还在栈中。
  3. 结合中缀式区分:在目标操作数之后才出现的运算符还没入栈,其余的按出栈顺序倒着排就是栈内容(栈底→栈顶)。
  4. 别忘了还没消解的 ( 也在栈里。

后缀表达式求值

  1. 操作数入栈;遇运算符连退两个。
  2. 先退出的记作 (右操作数),后退出的记作 (左操作数),算 。
  3. 结果压回栈;扫描完毕栈顶即答案。

数递归调用次数 / 执行次序

  1. 画递归调用树,根是最外层调用。
  2. 调用次数 = 树中结点总数(含叶子)。
  3. 第 个被执行的函数 = 树的先序遍历第 个结点。

边界

错题复盘:缓冲区是队列,不是栈

王道 3.3.6 第 1 题:栈的应用不包括( )。答案 D(缓冲区)。缓冲区是用队列实现的,A 递归、B 表达式求值、C 括号匹配都是栈的典型应用。 配套的第 3 题「下面( )用到了队列」答案是 D(FIFO 页面替换算法),其余的(括号匹配、表达式求值、递归)都只用到了栈。 2009 真题(第 12 题)考的是同一条:打印数据缓冲区的逻辑结构应该是队列——「在提取数据时必须保持原来数据的顺序,所以缓冲区的特性是先进先出」。

错题复盘:运算数栈的深度不是操作数个数

王道 3.3.6 第 4 题:利用栈求表达式的值时设立运算数栈 OPEN,假设 OPEN 只有两个存储单元,则下列表达式中不会发生溢出的是( )。答案 B((A-B)*C-D)。 B 的过程: 入栈、 入栈、计算得 、 入栈、计算得 、 入栈、计算得 ——栈深始终为 2。选项 A、C、D 依次算得栈深 4、3、3。 要点:中间结果 回到栈里仍然占一个单元,所以「先算完再取下一个操作数」的表达式栈深小,「必须把好几个操作数都读进来才能开始算」的栈深大。 【技巧】王道给的等价数法:统计已依次入栈但还未参与计算的运算符的个数。以 C 为例,(、A、- 入栈时 ( 和 - 还未参与运算,运算符栈大小为 2;B、* 入栈时为 3;C 入栈时 B*C 运算,回落到 2。

错题复盘:中缀转后缀时运算符栈的最大个数为 5

王道 3.3.6 第 13 题(2012 统考真题):将 a+b-a*((c+d)/e-f)+g 转换为等价的后缀表达式 ab+acd+e/f-*-g+ 时,用栈来存放暂时还不能确定运算次序的操作符,转换过程中同时保存在栈中的操作符的最大个数是( )。答案 A(5)。 求栈中操作符的最大个数时,为简单起见,可省略对操作数的处理——只画运算符和界限符。13 步过程中栈内最大是 -*((+。 两个 ( 都算数:界限符没消解前一直占着栈,这是最容易漏掉的一格。

错题复盘:扫描到 f 时栈中元素依次是 +(-*

王道 3.3.6 第 14 题(2014 统考真题):将 a/b+(c*d-e*f)/g 转换为后缀表达式的过程中,当扫描到 f 时,栈中的元素依次是( )。答案 B(+(-*)。 完整后缀式为 ab/cd*ef*-g/+。画表关键几步:/ 入栈 → + 优先级低于 /,弹出 / 后 + 入栈 → ( 入栈 → 栈顶是 (,* 直接入栈 → - 优先级低于 *,弹出 * 后 - 入栈 → * 优先级高于 -,* 入栈 → 此时扫描到 f,栈内自栈底到栈顶为 + ( - *。 【另解】不画表的反推法:中缀转后缀时操作数都直接输出,因此操作数的顺序是固定的。 扫描到 f 时,后缀式中 f 后面的运算符要么还未入栈、要么还在栈中。f 后面依次出栈的运算符为 *、-、/、+,其中 / 在中缀式里位于 f 之后、此时还未入栈,因此栈中的运算符(栈底→栈顶)为 +、-、*;此外已入栈的界限符 ( 此时还未消解,也在栈中,故栈中元素依次是 +(-*。 注意选项 A 是 +(*-,把 - 和 * 写反就中招——栈底到栈顶的顺序必须与压栈先后一致。

错题复盘:后缀求值退栈时,先退出的是右操作数

王道 3.3.6 第 18 题(2018 统考真题):栈 S1 保存整数、S2 保存运算符,F() 依次执行:① 从 S1 中依次弹出两个操作数 a 和 b;② 从 S2 中弹出一个运算符 op;③ 执行相应的运算 b op a;④ 将结果压入 S1。S1 中操作数依次是 (2 在栈顶),S2 中运算符依次是 *、-、+(+ 在栈顶)。调用 3 次 F() 后 S1 栈顶保存的值是( )。答案 B(15)。

  • 第 1 次:弹出 和 ,弹出 +,执行 ,压回 S1 剩 (5 在栈顶),S2 剩 *、-。
  • 第 2 次:弹出 和 ,弹出 -,执行 ,压回 S1 剩 (3 在栈顶),S2 剩 *。
  • 第 3 次:弹出 和 ,弹出 *,执行 S1 仅剩 。 题干里的 b op a 就是「后退出的作左操作数」。若按 a op b 算,第 2 次会得到 ,最终答案变成 (选项 A 就是这么设的陷阱)。

错题复盘:后缀表达式要按运算优先级逐步变换,不能从左往右硬凑

王道 3.3.6 第 2 题:表达式 a*(b+c)-d 的后缀表达式是( )。答案 B(abc+*d-)。 【另解】将两个直接操作数用括号括起来,再将操作符提到括号后,最后去掉括号:①②③,提出操作符并去掉括号后得 abc+*d-。 干扰项 A abcd*+- 是按「先把所有操作数抄一遍再补运算符」凑出来的,运算符的顺序完全错了。 王道在此处给出跨章提示:学完第 5 章后,可将表达式画成二叉树的形式,再用后序遍历即可求得后缀表达式。

错题复盘:2024 那道题画树比手算快

王道 3.3.6 第 19 题(2024 统考真题):与表达式 等价的后缀表达式是( )。答案 A()。 王道的标准解法:根据中缀表达式画出对应的二叉树,对该二叉树进行后序遍历即可得到后缀表达式。 树形为根 +,左子树 ,右子树根 /(左 *、右 ),* 的左 、右 -(左 、右 )。 干扰项 B 把 * 和 / 的先后弄反了: 先算,再除以 ,所以 * 必须在 / 之前。 选项 C、D 是前缀式,题目问的是后缀式——看清问的是哪一种。

错题复盘:递归调用次数要数整棵调用树的结点

王道 3.3.6 第 6 题:int F(int n){if(n<=3) return 1; else return F(n-2)+F(n-4)+1;},计算 F(8) 需要调用该递归函数的次数为( )。答案 C(9)。 递归调用树:;;两个 各自 。数一数结点共 9 个。 配套的第 7 题问的是「第几个被执行」:func(func(5)) 中,先执行内层 func(5)=func(3)+func(1),共执行 3 次;然后 func(func(5))=func(4),因此第 4 个被执行的是 func(4)(答案 C)。 一条通法:某个函数的执行次序等于其在递归调用树的先序遍历中的次序。

错题复盘:自栈底到栈顶是 main()、S(1)、S(0)

王道 3.3.6 第 15 题(2015 统考真题):int S(int n){return (n<=0)?0:S(n-1)+n;},main() 中 cout<<S(1);,程序运行时使用栈来保存调用过程的信息,自栈底到栈顶保存的信息依次对应( )。答案 A(main()→S(1)→S(0))。 递归调用函数时,在系统栈中保存的函数信息需满足先进后出的特点,依次调用了 main()、S(1)、S(0),所以自栈底到栈顶就是这个顺序。 选项 D 是反的(S(1)→S(0)→main())——「自栈底」三个字是本题唯一的坑。 王道注意框:在递归中,系统为每一层的返回点、局部变量、传入实参等开辟了递归工作栈来存储。 配套的第 9 题「执行函数时其局部变量一般采用( )进行存储」答案 C(栈结构)。

错题复盘:消除递归不一定需要使用栈

王道 3.3.6 第 11 题:下列说法中正确的是( )。答案 A(消除递归不一定需要使用栈)。 使用栈可以模拟递归的过程以消除递归,但对于单向递归和尾递归而言,可以用迭代的方式来消除递归。 其余三项:B「对同一输入序列进行两组不同的合法入栈和出栈组合操作,所得的输出序列也一定相同」错;C「通常使用队列来处理函数或过程调用」错(是栈);D「队列和栈都是运算受限的线性表,只允许在表的两端进行运算」错——只有队列允许在表的两端进行运算,而栈只允许在栈顶方向进行操作。

错题复盘:递归的效率低于非递归

王道 3.3.6 第 8 题:对于一个问题的递归算法求解和其相对应的非递归算法求解,( )。答案 B(非递归算法通常效率高一些)。 理由是递归算法在计算机实际执行的过程中包含很多的重复计算( 中 被算 3 次),另外还有每层调用的入栈出栈开销。 但「效率低」不等于「没用」:教材同时强调递归代码简单、容易理解,第 5 章的树因为用了递归思想「代码变得十分简单」。

口径差异:教材只讲中缀转后缀,不讲中缀转前缀

教材在 3.3.2 的脚注里写明:「中缀转前缀的方法类似,且统考真题仅考查过中缀转后缀的过程,因此本节仅介绍中缀转后缀的方法。」 所以选择题里出现前缀式,通常是干扰项(2024 真题的 C、D 就是两个前缀式)。真要转前缀,把加括号法的第 ② 步改成「运算符移至对应括号的前面」即可。

口径差异:王道的括号匹配代码有两处小疏漏,按思路答不按代码抄

综合应用题第 1 题的参考代码里 case ')' 用的是 Pop(S,e) 而其余两个分支写成小写 pop(S,e),且三个右括号分支在出栈前都没有先判栈空——若输入以右括号开头(如 )a(),对空栈出栈是未定义行为。 答题时按解析给的思路写:遇左括号入栈;遇右括号先判栈空(空则直接返回 false),再出栈比对;扫描结束后栈非空也是错误。 教材的思路本身是完整的:「扫描每个字符,遇到花、方、圆的左括号时入栈,遇到花、方、圆的右括号时检查栈顶元素是否为相应的左括号,若是出栈,否则配对错误。最后栈若不为空也是错误。」

对照速查

说法判断说明
「缓冲区是栈的应用」❌是队列(2009 真题);栈的应用是递归、表达式求值、括号匹配
「后缀表达式中括号可以省略」✅后缀式考虑了运算符优先级,只有操作数和运算符
「中缀表达式的括号也可以省略」❌中缀表达式中的括号是必需的
「后缀表达式 = 表达式树的后序遍历」✅前缀 = 先序;这是 2024 真题的标准解法
「中缀转后缀时操作数的顺序会变」❌操作数直接输出,相对顺序固定不变——这是校验答案最快的一招
「求运算符栈最大个数时不用管 (」❌界限符 ( 也占栈位(2012 真题最大值 5 里含两个 ()
「后缀求值时先退出的是左操作数」❌先退出的是右操作数 ,算 (2018 真题)
「运算数栈深度 = 操作数个数」❌中间结果也占位,(A-B)*C-D 只要深度 2(3.3.6 第 4 题)
「函数调用信息自栈顶到栈底是 main 在最上面」❌main 在栈底(2015 真题)
「递归的局部变量存在堆里」❌存在系统栈(递归工作栈)里,连同返回点和传入实参
「消除递归必须用栈」❌单向递归和尾递归可用迭代消除(3.3.6 第 11 题、2017 真题 I)
「递归比非递归效率高」❌反了;递归含大量重复计算,但代码简单
「层次遍历和 BFS 都要用队列」✅3.3.4;图的 BFS 见 6.3
「括号匹配算法结束时栈非空也算匹配」❌栈必须为空,否则有左括号未消解

考点

年份考法落点
2009打印数据缓冲区的逻辑结构队列(先进先出)
2012中缀转后缀过程中运算符栈的最大个数5(含两个 ()
2014中缀转后缀某一时刻的栈内容+(-*;反推法可秒
2015函数调用栈自栈底到栈顶的内容main()→S(1)→S(0)
2016多队列(火车轨道)最少条数4;见 3.1.1 的超纲补充
2017关于栈的叙述判错「非递归必须用栈」是错项
2018双栈实现表达式求值,b op a15;操作数退栈次序
2024中缀转后缀画表达式树后序遍历

复习动作:① 把 A+B*(C-D)-E/F 的 14 步表和 12 步求值表各默写一遍;② 用反推法重做 2014,再用画表法验一遍;③ 画 F(5) 和 F(8) 的递归调用树,数结点数和先序次序;④ 把 3.3.6 第 4 题四个选项的运算数栈深度都算出来。

链接