拓扑排序(Topological Sort)
定义
拓扑排序是对有向无环图(DAG, Directed Acyclic Graph) 中所有节点进行线性排序的算法,使得对于图中每条有向边 u → v,节点 u 在排序结果中都出现在节点 v 之前。
⚠️ 拓扑排序只适用于有向无环图,如果图中存在环,则无法进行拓扑排序。
直观理解
想象一组任务,某些任务必须在其他任务之前完成:
穿袜子 → 穿鞋子
穿内衣 → 穿衬衫 → 穿外套
穿裤子 → 穿鞋子
拓扑排序就是找到一个合法的执行顺序,例如:
穿内衣 → 穿袜子 → 穿裤子 → 穿衬衫 → 穿外套 → 穿鞋子
两种经典算法
1. Kahn 算法(BFS 方式)
核心思想:不断移除入度为 0 的节点
python
from collections import deque
def topological_sort_kahn(graph, in_degree):
queue = deque()
result = []
# 将所有入度为 0 的节点加入队列
for node in in_degree:
if in_degree[node] == 0:
queue.append(node)
while queue:
node = queue.popleft()
result.append(node)
for neighbor in graph[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
# 如果结果长度不等于节点数,说明有环
if len(result) != len(in_degree):
return [] # 存在环
return result
步骤:
- 计算所有节点的入度
- 将入度为 0 的节点入队
- 出队一个节点,将其邻居的入度 -1
- 若邻居入度变为 0,则入队
- 重复直到队列为空
2. DFS 算法(深度优先搜索)
核心思想:DFS 后序遍历,逆序即为拓扑序
python
def topological_sort_dfs(graph):
visited = set()
stack = []
def dfs(node):
visited.add(node)
for neighbor in graph.get(node, []):
if neighbor not in visited:
dfs(neighbor)
stack.append(node) # 后序加入
for node in graph:
if node not in visited:
dfs(node)
return stack[::-1] # 逆序输出
两种算法对比
| 特性 | Kahn(BFS) | DFS |
|---|---|---|
| 实现方式 | 迭代 | 递归 |
| 环检测 | ✅ 天然支持 | 需额外标记 |
| 时间复杂度 | O(V + E) | O(V + E) |
| 空间复杂度 | O(V) | O(V) |
| 结果唯一性 | 不唯一 | 不唯一 |
应用场景
| 场景 | 说明 |
|---|---|
| 📦 包管理器 | npm、pip 解析依赖安装顺序 |
| 🏗️ 构建系统 | Make、Gradle 确定编译顺序 |
| 📚 课程规划 | 先修课程问题 |
| 🔄 任务调度 | 确定任务执行顺序 |
| 🗃️ 数据库 | 外键约束的表创建顺序 |
总结
拓扑排序 = 对 DAG 节点排序,保证所有边从前指向后
核心性质:有向 + 无环 → 才能拓扑排序
两种算法:Kahn(BFS入度法)/ DFS(后序逆置法)
时间复杂度:O(V + E)