
视频元信息
- 课程: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)的通用求值规则。
- 数字求值为自身;符号在模型中将"消失"(在需要时被替换掉)。
组合式求值规则(三步)
- 求值运算符(operator)得到过程(procedure)(如
+是原语过程,复合过程亦可)。 - 求值运算对象(operands)得到实参(arguments)。
-
应用过程于实参:复制过程体,把形参(formal parameters)代换为实参,再求值得到的新过程体。
-
重点:形参是过程声明中定义的名称;代换就是把实参塞进这些名字的位置。
- 规则非常简单,接下来一段时间要机械地、形式化地执行它——遇到不懂的地方,就照这个机械步骤做。
四、逐步演示: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。
- 3 不为 0 →
- 提醒:以后不再逐步写出
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"):
- 假设能神奇地把上面 n-1 层从 from 移到 spare;
- 移动最大的第 n 个盘从 from 到 to;
- 再把 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