问题与目标
目录、组织机构和语法结构具有层级;任务依赖、交通网络和知识关系可能彼此连接。树和图为这些关系提供统一表示,遍历算法则回答“从哪里能够到达哪里”。
完成标准:能用邻接表表示图,写出深度优先和广度优先遍历,并理解递归的终止条件。本篇不展开平衡树和最短路径等高级算法。

左侧树结构强调单一父节点和清晰层级,右侧图结构允许多条关联路径甚至形成环。结构差异决定了遍历时是否需要记录已经访问过的节点。
核心概念
树是没有环的层级结构,常用根、父节点、子节点、叶子和深度描述。二叉树的每个节点最多两个子节点;二叉搜索树还要求左侧值较小、右侧值较大。
图由顶点和边组成,可以有向或无向、带权或无权。邻接表只记录实际存在的边,适合多数稀疏关系;邻接矩阵查询两点是否相连方便,但需要 O(V²) 空间。
深度优先搜索 DFS 沿一条路径深入,适合递归结构、连通性和回溯;广度优先搜索 BFS 逐层扩展,在无权图中可得到最少边数路径。存在环时必须记录已经访问的节点。
树的遍历顺序
二叉树常见遍历有:
- 前序:节点 → 左子树 → 右子树,适合复制结构。
- 中序:左子树 → 节点 → 右子树,二叉搜索树会得到有序结果。
- 后序:左子树 → 右子树 → 节点,适合先处理子节点再处理父节点。
- 层序:逐层访问,本质是 BFS。
from dataclasses import dataclass
@dataclass
class TreeNode:
value: int
left: "TreeNode | None" = None
right: "TreeNode | None" = None
def inorder(node: TreeNode | None) -> list[int]:
if node is None:
return []
return [*inorder(node.left), node.value, *inorder(node.right)]
root = TreeNode(4, TreeNode(2, TreeNode(1), TreeNode(3)), TreeNode(6))
print(inorder(root))
输出 [1, 2, 3, 4, 6]。递归基线是空节点,每次调用都进入更小子树。
图的表示成本
邻接表的空间约为 O(V+E);邻接矩阵需要 O(V²)。稀疏依赖网络适合邻接表,节点较少且经常查询任意两点是否直接相连时,矩阵可能更直观。有向依赖图还应检测环,否则无法得到合法执行顺序。
可运行实现
给定任务依赖图,输出从 collect 出发的遍历顺序和到 report 的最短依赖路径:
from collections import deque
graph = {
"collect": ["clean", "validate"],
"clean": ["analyze"],
"validate": ["analyze"],
"analyze": ["chart", "report"],
"chart": ["report"],
"report": [],
}
def dfs(node: str, visited: set[str] | None = None) -> list[str]:
if visited is None:
visited = set()
if node in visited:
return []
visited.add(node)
order = [node]
for neighbor in graph[node]:
order.extend(dfs(neighbor, visited))
return order
def shortest_path(start: str, target: str) -> list[str] | None:
queue = deque([(start, [start])])
visited = {start}
while queue:
node, path = queue.popleft()
if node == target:
return path
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, [*path, neighbor]))
return None
print("DFS:", dfs("collect"))
print("path:", shortest_path("collect", "report"))
BFS 输出的路径应为 collect → clean/validate → analyze → report,共三条边。具体经过 clean 还是 validate 由邻接表顺序决定。
输入是六个任务及其有向依赖,输出是 DFS 访问顺序和一条最少边数路径。验证时再加入一条回到 collect 的边,确认 visited 能阻止无限循环。
递归函数必须包含基线条件,并确保每次调用更接近它。很深或不受控的关系图优先使用显式栈,避免递归深度限制。
进一步验证
- 向依赖图加入孤立节点,说明从
collect出发为何不会访问它。 - 加入一条回边构造环,分别测试 DFS 和 BFS 是否终止。
- 将 BFS 路径中的整条列表复制改为记录父节点,在找到目标后回溯路径。
验证时要写清边的方向、起点可达范围、是否允许环,以及所求是最少边数还是最小权重。
常见问题与排查
- 图遍历无限循环:没有维护
visited,或加入时机太晚。 - 把树当作任意图:图可能有环、多个入口和多条路径。
- BFS 队列使用列表头部删除:改用
deque.popleft()。 - 递归漏掉返回值:逐层打印参数和返回结果,验证递归不变量。
- 用 BFS 处理带权最短路径:边权不同时需要 Dijkstra 等其他算法。
- 把依赖方向写反:先明确
A -> B表示 A 依赖 B,还是 A 完成后才能执行 B。 - 递归创建大量列表:教学写法清楚,但大树可使用生成器或显式栈减少复制。
小结
树突出层级,图表达一般关系,递归适合描述自相似结构。可靠遍历要明确访问顺序、已访问集合、终止条件和路径是否需要保存。
许可协议:CC BY-NC 4.0
更新于 1 小时前
觉得文章有帮助?点个赞吧!
0 条评论


