Ran Wei/计算机科学系列/07
English
计算机科学基础 — Ran Wei

模块 07: 递归、树与图

跟踪递归调用,遍历树与图,构建路径查询,并明确最短路径的衡量标准。

约 5 小时4 个时段2 个实验8 道练习6 道自测题

完成后你能够

  • 识别递归基础情况与推进。
  • 跟踪调用栈。
  • 遍历二叉树。
  • 用访问状态实现 BFS。
  • 合理选择 BFS 或带权最短路径。

开始之前

建议先修模块: 04, 06.

了解栈、队列与字典成员检查。元组组合值,None 表示空子树。

目录

学习计划

5 小时

四个 75 分钟时段,包含练习。拓展任务或不熟悉的先修知识可能需要更多时间。进度本地保存,两种语言共享。

时段 275 分钟
构建与探究
时段 375 分钟
应用与拓展
时段 475 分钟
推理与复习
1

递归与调用栈

递归函数在更小子问题上调用自身,基础情况不再调用。非负阶乘满足 fact(0)=1、fact(n)=n×fact(n−1)。每个未完成调用在栈中保存局部状态,返回时逐层展开。需证明非负度量递减,仅有基础情况不保证终止。深递归消耗栈,Python 限制深度,大输入可用迭代或显式栈。

检查理解

改为 fact(n+1) 有何问题?

完整解答

参数远离零,无法达到基础情况。

2

树与遍历

有根树有根及父子结构,无环。二叉树每节点最多两子节点。前序为节点、左、右,中序为左、节点、右,后序为左、右、节点。二叉搜索树左右键满足大小约束,所以中序有序。高度控制搜索成本:平衡树对数高度,链形树线性高度。实验用嵌套元组,而非完整可变搜索树。

检查理解

中序能排序任意二叉树吗?

完整解答

不能,需满足搜索树顺序性质。

3

图与表示

图由顶点和边组成,边可有向、无向或带权。邻接表通常 O(V+E) 存储,邻接矩阵 O(V²) 并支持直接查边。图可有环和多路径,遍历需访问状态。安排处理时就标记,避免重复入队。需规定孤立点、未知端点及平行边。

检查理解

为何入队时标记,而非只在出队时?

完整解答

防止多个前驱重复安排同一顶点。

4

深度与广度搜索

DFS 先深入分支,用递归或显式栈。BFS 用队列,按到起点的边距离非递减访问。邻接表和常数访问检查下,两者 O(V+E)。BFS 首次发现节点时保存前驱,逆向追踪可恢复路线。DFS 可找路线,但通常不保证边数最少。

检查理解

哪种前沿结构保证 BFS 顺序?

完整解答

先进先出队列。

5

带权路径与限制

BFS 在等边成本时最小化边数。非负不同权重可用 Dijkstra,反复确定暂定距离最小节点并松弛出边,优先队列帮助选择。成本百的一条边输给两条成本一的边。负权破坏 Dijkstra 的确定论证,需要其他算法。称最短之前需说明目标,并区分不可达与未知顶点。

检查理解

道路时间不同时 BFS 最小化时间吗?

完整解答

不,它最小化边数而非不同权重总时间。

6

常见误解

  • 返回列表的树实验用于教学,反复拼接不是最优遍历。
  • 最短路线必须规定成本模型。
7

实验准备

下载脚本,在终端中使用 Python 3.11 或更新版本运行:python m07_tree.py. Windows 也可使用 py -3;部分系统使用 python3。实验仅用标准库。先预测结果,再运行并完成变体。不要使用 -O,以保留断言。下方输出由构建器实际运行捕获,两种语言使用相同代码与输出。

8

实验 1 — 递归树

画树、预测遍历与高度,跟踪 None 基础情况。

下载 m07_tree.py

"""Recursive traversal with an explicit empty-tree base case."""
def inorder(node):
    if node is None:
        return []
    value, left, right = node
    return inorder(left) + [value] + inorder(right)

def height(node):
    if node is None:
        return 0
    return 1 + max(height(node[1]), height(node[2]))

tree = (4, (2, (1, None, None), (3, None, None)), (6, None, None))
print("inorder:", inorder(tree))
print("height:", height(tree))
assert inorder(tree) == [1, 2, 3, 4, 6]
assert inorder(None) == [] and height(None) == 0
实际运行输出
inorder: [1, 2, 3, 4, 6]
height: 3
  1. 实现前序。
  2. 构建三节点链。
  3. 解释反复拼接列表的复制成本。
完整解答

前序先 value,再左、右。三节点链高度三。返回列表版本反复复制部分结果,斜树上可二次;用累积列表每项追加一次可避免。

9

实验 2 — 路线查询

图包含环与孤立点,BFS 发现时记录前驱,不可达返回 None。

下载 m07_routes.py

"""BFS finds a shortest path measured in edges in an unweighted graph."""
from collections import deque
def route(graph, start, goal):
    if start not in graph or goal not in graph:
        raise KeyError("unknown vertex")
    queue, parent = deque([start]), {start: None}
    while queue:
        vertex = queue.popleft()
        if vertex == goal:
            path = []
            while vertex is not None:
                path.append(vertex); vertex = parent[vertex]
            return path[::-1]
        for neighbour in graph[vertex]:
            if neighbour not in parent:
                parent[neighbour] = vertex; queue.append(neighbour)
    return None

graph = {"A": ["B", "C"], "B": ["A", "D"], "C": ["A", "D"], "D": ["B", "C"], "E": []}
print("A to D:", route(graph, "A", "D"))
print("A to E:", route(graph, "A", "E"))
assert route(graph, "A", "D") == ["A", "B", "D"]
assert route(graph, "A", "A") == ["A"]
assert route(graph, "A", "E") is None
实际运行输出
A to D: ['A', 'B', 'D']
A to E: None
  1. 交换 A 邻居顺序。
  2. 加入 D 与 E 的双向边。
  3. 查询未知点,对比不可达 E。
完整解答

另一等长路线为 A,C,D。连接 E 后为 A,B,D,E。未知点抛 KeyError,有效但断开的点返回 None。邻居顺序影响平局,不改变距离。

10

练习与完整解答

先尝试,再展开解答。★ 应用概念;★★ 结合概念;★★★ 进行设计或证明。

练习 1 — 基础情况★

空树节点计数基础情况?

完整解答

None 返回零,否则一加左右计数。

练习 2 — 遍历★

根 B,子 A、C,给中序与前序。

完整解答

中序 A,B,C,前序 B,A,C。

练习 3 — 环★★

无访问状态时 DFS 为何在 A→B→A 循环?

完整解答

不断重访,无递减未访问集合。探索邻居前标记。

练习 4 — 存储★★

稀疏图哪种表示更小?

完整解答

E 远小于 V² 时,O(V+E) 邻接表更小。

练习 5 — 带权反例★★

直达成本十,经过 B 成本四,BFS 偏好哪个?

完整解答

直达一条边,却非最低成本。带权最短应选四。

练习 6 — BFS 证明★★★

BFS 首次发现路线为何边数最少?

完整解答

队列先处理距离 d 再 d+1,新邻居距离 d+1。更短前驱应早已处理,故不会后来才出现更短发现。

练习 7 — 栈空间★★★

比较平衡树与链树递归栈深。

完整解答

深度等于高度:平衡 O(log n),链 O(n),都访问 n 节点。

练习 8 — 路径恢复★★

为何反转累积前驱路径?

完整解答

沿前驱从终点到起点,反转才是起点至终点。

11

自测

选择答案查看反馈,重置后可重做。无需 JavaScript 也可阅读答案表。

1

递归需要什么?

2

BFS 用什么?

3

BFS 最小化什么?

4

Dijkstra 要求什么?

5

图遍历为何需访问状态?

6

中序何时产生排序键?

答案表
  1. A — 推进让基础可达。
  2. B — FIFO 扩展层。
  3. C — 等成本边使边距离有意义。
  4. A — 负权破坏确定论证。
  5. B — 防止重复安排。
  6. C — 左右键不变式必要。
12

引导阅读

13

复习与下一步

跟踪不可达与等长路径的前驱,解释递归空情况。模块 08 将比较更广泛设计策略。

14

关键术语

术语含义
前沿已发现但待处理顶点。
松弛通过边改善暂定距离。
高度此处为根至叶路径最大节点数。