视频封面

视频元信息

  • 课程:MIT 6.001 Structure and Interpretation of Computer Programs(SICP),1986 年原版
  • 本讲主题:过程与代换模型(Procedures and the Substitution Model)

内容摘要(TL;DR)

本讲建立"程序文本"与"过程行为"之间的桥梁,核心是代换模型:求值组合式时,先求值运算符得到过程、求值运算对象得到实参,再把过程体中的形参代换为实参并求值。通过"求和"与"斐波那契"两个例子,引出迭代过程与递归过程在时间/空间复杂度上的本质区别,并用"官僚机构"类比和微分方程画圆等例子加深直觉。


一、程序员的职责:连接"咒语"与"过程" [00:01]

程序员的工作是设计过程(process),以完成特定目标,例如求平方根等。实现方式是用过程(procedure)和表达式(expression)构造"咒语(spells)",这些咒语又指挥过程去达成目标。

  • 重点:要有效编程,必须理解自己写下的文本(咒语)与它所要控制的过程行为之间的关系。
  • 本讲目标:尽可能清晰地建立这种连接——理解特定的过程与表达式模式如何导致特定的执行模式(过程行为)。

考点:本讲的核心命题是"程序文本 → 过程行为"的映射关系。后续课程中反复出现的"过程形状(shape of process)"直觉,都由此建立。


二、代换模型:理解过程的最简工程模型 [01:23]

从一个简单程序开始:定义 sum-of-squares(x, y) = square(x) + square(y),其中 square(x) = x * x。求值 sum-of-squares(3, 4) 期望得到 25。

为了理解"这怎么会发生",需要一个形式化或半形式化的机械模型,说明机器原则上如何做到这一点。

  • 类比:就像电阻的欧姆定律模型 V = IR,它近似为真、并非绝对真理(电流过大电阻会"爆"),但对某些目的足够适用。
  • 理解:代换模型(substitution model)是我们理解"过程如何工作、过程如何产生过程"的最简单模型,在未来几天内对大多数问题足够准确。
  • 提醒:最终会无法维持"机器就是这样工作"的幻觉,届时需要更具体、更细致的模型。

易错点:代换模型是教学模型,不是真实机器的实现方式。不要把它当作计算机内部实际行为的精确描述。


三、表达式的种类与组合式的求值规则 [03:55]

首先梳理语言中出现的表达式种类:

  • 数字(numbers)
  • 符号(symbols)
  • lambda 表达式(本讲暂时略过,后续再讲)
  • 定义(definitions)
  • 条件式(conditionals)
  • 组合式(combinations)

  • 定义、条件式等属于特殊形式(special forms),各有自己的规则。

  • 本讲重点讲组合式(combination)的通用求值规则。
  • 数字求值为自身;符号在模型中将"消失"(在需要时被替换掉)。

组合式求值规则(三步)

  1. 求值运算符(operator)得到过程(procedure)(如 + 是原语过程,复合过程亦可)。
  2. 求值运算对象(operands)得到实参(arguments)。
  3. 应用过程于实参:复制过程体,把形参(formal parameters)代换为实参,再求值得到的新过程体。

  4. 重点:形参是过程声明中定义的名称;代换就是把实参塞进这些名字的位置。

  5. 规则非常简单,接下来一段时间要机械地、形式化地执行它——遇到不懂的地方,就照这个机械步骤做。

四、逐步演示:sum-of-squares(3, 4) 的代换过程 [07:23]

  • 求值 sum-of-squares(3, 4):把 3 代换 x、4 代换 y,进入过程体(一个加法组合式)。
  • 归约为:square(3) + square(4)(一次归约步骤 reduction step)。
  • 求值运算对象时,任选一个顺序(这里从右到左):
    • square(4):把 4 代换 square 的形参 x,得 4 * 4。
    • 于是表达式变为 square(3) + (4 * 4)。
  • 把乘法视为原语(primitive),不再展开。
  • 继续:square(3) → 3 * 3,于是 (3 * 3) + 16 → 9 + 16 → 25。

重点(工程智慧):"理解复杂事物的关键,是知道不去看什么、不去算什么、不去想什么。" —— 即学会忽略细节。机器内部任何一层往下看都有更多层次,但我们必须学会在合适的抽象层停下来。

求值顺序的两种可能

  • 上面用的是应用序(applicative order):先求值运算符和运算对象,再做应用。
  • 理解:还存在正则序(normal order):先把表达式(未求值的运算符/运算对象)代换进过程体,最后才求值,也能得到相同答案。
  • 当前选择应用序,是因为真实机器就是这么做的;但顺序在某种程度上是任意的,后续课程会讨论选择不同顺序的理由。

五、条件式的求值规则 [10:53]

条件式(if)的三个组成部分,给部分命名很重要——"有了名字就有了掌控力":

  • 谓词(predicate):求值为真或假。
  • 推论(consequence):谓词为真时求值的表达式。
  • 替代(alternative):谓词为假时求值的表达式。

规则:先求值谓词;若为真,求值推论,否则求值替代。


六、案例:皮亚诺式加法(Peano arithmetic) [12:11]

写一个反复出现的经典程序——用递增/递减实现加法:

sum(x, y):
  if x == 0: return y
  else: return sum(decrement(x), increment(y))
  • 理解:本质是"两堆弹珠",从一堆搬到另一堆,搬空一堆时另一堆就是和。
  • 代换演示 sum(3, 4):
    • 3 不为 0 → sum(2, 5) → sum(1, 6) → sum(0, 7) → 7。
  • 提醒:以后不再逐步写出 if 的中间代换(太繁琐),直接说"3+4 归约为 2+5,再归约为……"。
  • 重点:课程中会不加思索地引入越来越多的原语操作(increment、decrement 等)。它们是否原语并不重要——被告知它们做什么,就当作已知。

七、两种"加法"程序:迭代 vs 递归 [19:15]

两个几乎完全相同的程序,只差几个字符的位置:

程序 A(迭代版)

sum(x, y):
  if x == 0: return y
  else: return sum(decrement(x), increment(y))

程序 B(递归版)

sum(x, y):
  if x == 0: return y
  else: return increment(sum(decrement(x), y))
  • 理解(弹珠类比):程序 A 是"把弹珠从一堆搬到另一堆,搬空即得和";程序 B 是"先把要加的东西攥在手里(推迟处理),等把一堆减到 0,再慢慢加回来"。

过程展开对比

程序 A 的过程(线性迭代)

sum(3,4) → sum(2,5) → sum(1,6) → sum(0,7) → 7
  • 每一步都是自包含的,状态完全由形参 x、y 记录。
  • 时间:正比于 x。空间:常数(order of 1)。

程序 B 的过程(线性递归)

sum(3,4)
= inc(sum(2,4))
= inc(inc(sum(1,4)))
= inc(inc(inc(sum(0,4))))
= inc(inc(inc(4))) = 7
  • 时间:也正比于 x。
  • 空间:正比于 x(有一串"被推迟的 increment"堆着)。
  • 名称:线性递归过程(linear recursion)——时间与空间都正比于输入参数。

易错点(核心):递归定义 ≠ 递归过程。上面两个程序都是递归定义(定义中引用了自身),但一个产生迭代过程,一个产生递归过程。定义是否递归,与它演化出的过程是否递归,没有必然联系。


八、官僚机构类比:迭代与递归的直觉 [35:00]

  • 迭代(iteration):GGS(即"我")想算 3+4,找人代劳并"把结果还给我";被找的人也懒,转手把问题变成 2+5 再找人……每个人都不想再看到这张纸条,最后有人直接回答"7"并传回。没有未完成的工作堆积。
  • 递归(recursion):Joe 说"我会记住待会儿要加 1",把 2+4 交给 Harry 并要 Harry 把结果送回 Joe;Harry 回来给 6,Joe 再做 increment 得 7。桌面上堆着更多未处理的纸条(更多被推迟的操作)。

  • 理解(状态视角):若在计算中途"杀掉计算机"——

    • 迭代:所有状态都在显式变量中,可以从当前点继续。
    • 递归:除了形参变量里的信息,还有"藏在桌子底下"的信息(被推迟的操作),仅凭 sum(1,4) 无法恢复原问题 3+4 的完整计算。
  • 思考(物理类比):微分方程画圆。状态为 (x, y),导数 dy = x·dt、dx = -y·dt,不断迭代更新状态即可画出圆。注意:视频中用的是前向欧拉积分器,它会逐渐螺旋发散,只能画一阵子圆。这是"迭代 = 状态全在显式变量里"的绝佳例子。

九、斐波那契:指数级树形递归 [47:00]

斐波那契数列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55……每个数是前两个之和(源自"兔子增长"模型)。

最直接的递归定义:

fib(n):
  if n < 2: return n
  else: return fib(n-1) + fib(n-2)

过程展开(以 fib(4) 为例)

fib(4)
├─ fib(3)
│  ├─ fib(2)
│  │  ├─ fib(1) = 1
│  │  └─ fib(0) = 0
│  └─ fib(1) = 1
└─ fib(2)
   ├─ fib(1) = 1
   └─ fib(0) = 0
  • 重点(性能灾难):fib(3) 被重复计算两次。每增加一步,计算量约按现有规模成比例翻倍——这是指数级时间的过程(时间约为 Θ(φⁿ),φ 为黄金比例)。
  • 空间复杂度:只需记住从根到当前节点的一条路径,最长路径长度正比于 n,故空间为 O(n)。
  • 理解:之所以时间必须这样增长,是因为在代换模型下顺序地检查了树中每一个节点,而节点数指数增长。

易错点:树形递归的"空间"取决于树的深度(路径长度),而非节点总数。别把指数级时间和指数级空间混为一谈。

  • 拓展:视频末尾提到记忆化(memoization)——把算过的 fib 值存进表里,下次直接查表(线性时间查找),可把问题转化为线性时间。此外还存在线性时间的迭代版本计算斐波那契,讲师要求学生课后自己推导。

十、汉诺塔:树形递归的又一实例 [59:00]

  • 问题:把 n 个圆盘从一根柱移到另一根柱,借助第三根柱,每次只能移动一个盘且大盘不能压在小盘上。传说东方有 64 层塔,需要 2⁶⁴ 步,完成时"世界末日"。
  • 递归思路("愿望式思维 wishful thinking"):
    1. 假设能神奇地把上面 n-1 层从 from 移到 spare;
    2. 移动最大的第 n 个盘从 from 到 to;
    3. 再把 n-1 层从 spare 移到 to。
  • 递归程序:
move-tower(n, from, to, spare):
  if n == 0: 什么都不做
  else:
    move-tower(n-1, from, spare, to)
    move-disk(from, to)
    move-tower(n-1, spare, to, from)
  • 重点:该程序与斐波那契程序在结构上"几乎相同"——都会产生指数级树形过程。
  • 视频演示了 4 层汉诺塔的移动序列(from→spare 等一串移动)。
  • 思考(开放问题):是否存在迭代版本的汉诺塔算法?即不把问题分解为两个子问题,而用更局部的规则一步步推进。讲师认为值得思考。

考点:汉诺塔、斐波那契是树形递归的典型代表,注意其时间复杂度的指数特征与递归定义、递归过程之间的关系。


十一、本讲小结与关键概念对照

概念 含义 时间 空间
线性迭代过程 状态全在显式变量中,自包含 O(n) O(1)
线性递归过程 有推迟的操作堆积 O(n) O(n)
树形递归过程 分支展开成树,节点重复计算 指数级 O(深度)
  • 重点:判断过程类型的核心标准不是"定义是否递归",而是是否存在被推迟的操作、状态是否全在显式变量中。
  • 理解:本讲建立的"程序文本 → 过程形状"直觉,是后续构造大型复杂系统的基石。

关键标签 / 关键词

代换模型(substitution model)、过程(process)、过程(procedure)、表达式(expression)、组合式(combination)、特殊形式(special form)、应用序(applicative order)、正则序(normal order)、条件式(conditional)、谓词/推论/替代、皮亚诺算术(Peano arithmetic)、线性迭代(linear iteration)、线性递归(linear recursion)、树形递归(tree recursion)、斐波那契(Fibonacci)、汉诺塔(Tower of Hanoi)、时间复杂度、空间复杂度、记忆化(memoization)、黄金比例(Golden Ratio)、微分方程画圆、愿望式思维(wishful thinking)、SICP、MIT 6.001


生成时间:2026-10-11 17:10:42 生成平台:VideoNote www.xygy.top