树与层级遍历
本节目标
用树表达单一根下的层级关系,掌握根、父子、叶、深度与子树,并分别用深度优先(DFS)和广度优先(BFS)遍历,同时限制不可信嵌套深度和节点数。
树:一个节点到多个子节点
树常用术语:根、父节点、子节点、叶节点、深度。文件目录、JSON 嵌套对象和决策步骤都可看作树。
tree = {
"label": "root",
"children": [
{"label": "option-a", "children": []},
{"label": "option-b", "children": []},
],
}遍历时要明确深度优先(先走到底)还是广度优先(逐层处理)。二者都会访问每个节点一次,差别在访问顺序和辅助空间。
迭代式 DFS
def node_parts(value: object) -> tuple[str, list[object]]:
if not isinstance(value, dict):
raise ValueError("树节点必须是对象")
if set(value) != {"label", "children"}:
raise ValueError("树节点字段必须恰好为 label 和 children")
label = value["label"]
children = value["children"]
if not isinstance(label, str) or not isinstance(children, list):
raise ValueError("树节点格式非法")
return label, children
def dfs_labels(root: object, *, max_nodes: int = 1_000) -> list[str]:
if max_nodes < 1:
raise ValueError("max_nodes 必须至少为 1")
result: list[str] = []
stack = [root]
while stack:
if len(result) >= max_nodes:
raise ValueError("树节点数超过上限")
node = stack.pop()
label, children = node_parts(node)
result.append(label)
# 逆序压栈,使原 children 的左侧先访问。
stack.extend(reversed(children))
return result显式栈避免用 Python 递归深度当作输入控制,但仍必须限制总节点数、最大深度和单节点子项数。这里为突出遍历省略了完整递归 schema 校验;不可信输入应在遍历前或遍历中逐层验证。
BFS 与层级
from collections import deque
def bfs_levels(
root: object, *, max_depth: int = 100, max_nodes: int = 1_000
) -> list[tuple[int, str]]:
if max_depth < 0 or max_nodes < 1:
raise ValueError("max_depth/max_nodes 上限非法")
result: list[tuple[int, str]] = []
queue = deque([(root, 0)])
while queue:
if len(result) >= max_nodes:
raise ValueError("树节点数超过上限")
node, depth = queue.popleft()
if depth > max_depth:
raise ValueError("树深度超过上限")
label, children = node_parts(node)
result.append((depth, label))
queue.extend((child, depth + 1) for child in children)
return resultBFS 适合逐层展示或找最浅满足条件节点;DFS 适合子树处理、路径搜索和显式进入/退出语义。两者都不是自动的“最佳选择”。
树、DAG 与普通图
树通常有一个根,每个非根节点只有一个父节点且不存在环。DAG 可让一个节点有多个前驱;普通图还可能含环。一个文件夹层级是树,但“笔记互相链接”通常是图。不要因为数据用嵌套 JSON 表示,就假定业务关系必然是树。
资源与安全边界
- 限制最大深度、总节点数、每节点子项数和标签长度;
- 不让用户输入直接控制无限递归;
- 若结构可共享节点或出现回边,使用
visited,否则它已不是严格树; - 明确输出顺序,不能依赖集合迭代;
- 计算路径时决定复制列表还是用父指针重建,避免大量路径副本。
练习
- 把一个三层文件目录画成树,标出根、内部节点、叶和最大深度。
- 对同一棵 7 节点树手算 DFS 与 BFS 顺序,再运行代码验证。
- 扩展 BFS:超过
max_depth立即失败,并为边界值写测试。 - 写
leaf_labels(root),不修改输入;测试空子项、单节点和多分支。 - 举例说明一个“看起来像层级”但实际允许多父节点的 DAG。
自测
- 树中的根、叶和深度分别是什么?
- DFS 与 BFS 各使用什么辅助结构?
- 迭代式 DFS 为什么仍需深度/节点上限?
- 多个父节点的层级关系还是严格树吗?
- 若输入可能含回边,为什么要使用
visited?
下一步与相关概念
下一节:图、依赖与拓扑顺序。
参考资料
获取日期:2026-07-14。