拓扑排序是一种用于有向无环图(DAG)的排序算法,其核心思想是将图中所有顶点排成一个线性序列,使得对于每一条有向边 (u, v),顶点 u 在序列中都出现在 v 之前。这一方法广泛应用于任务调度、课程安排、编译顺序等场景。
拓扑排序的基本步骤如下:
- 找出所有入度为 0 的顶点(即没有前驱的顶点),并将它们加入队列;
- 从队列中取出一个顶点输出,并删除该顶点及其发出的所有边;
- 更新这些边所指向顶点的入度,若某顶点入度变为 0,则将其入队;
- 重复上述过程,直到队列为空;
- 若输出的顶点数等于总顶点数,则说明图无环,排序成功;否则存在环,无法进行拓扑排序。
实现方式主要有两种:
- Kahn 算法(基于入度):使用队列维护入度为 0 的节点,适合广度优先处理。
- DFS 法:对图进行深度优先遍历,当某个顶点的所有邻接点都被访问后,将该顶点压入栈中。最后依次弹出栈中元素即为逆拓扑序列。
时间复杂度分析:
- 每个顶点和边仅被访问一次,因此时间复杂度为 $ O(n + e) $,其中 $ n $ 为顶点数,$ e $ 为边数。
AOE 网(Activity On Edge Network)详解:
AOE 网是一种带权有向无环图:
- 顶点表示事件(event),代表某些活动的完成;
- 有向边表示活动(activity),边上的权值表示活动所需的时间;
- 整个图表示一个工程的流程。
主要用途包括:
- 计算整个工程的最早完成时间(即关键路径长度);
- 找出关键路径:从源点到汇点的最长路径,路径上的活动称为关键活动,它们的延迟会直接影响整个工期;
- 优化资源分配:非关键活动有一定的“松弛时间”(float),可适当延迟而不影响总工期,便于资源调配。
关键路径分析涉及的概念:
- ve(j):事件 j 的最早发生时间;
- vl(j):事件 j 的最迟发生时间;
- e(i):活动 i 的最早开始时间;
- l(i):活动 i 的最迟开始时间;
- l(i) - e(i) = 0 的活动即为关键活动。
通过正向和反向两次遍历计算各事件和活动的时间参数,最终确定关键路径。
示例解析:若某 AOV 网的拓扑序列为 6,1,4,3,2,5,说明该图可以线性化,不存在回路,排序有效。
from collections import deque, defaultdict
def topological_sort(n, edges):
# 建图并统计入度
indegree = [0] * (n + 1)
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
indegree[v] += 1
# 入度为0的节点入队
queue = deque([i for i in range(1, n + 1) if indegree[i] == 0])
result = []
while queue:
u = queue.popleft()
result.append(u)
for v in graph[u]:
indegree[v] -= 1
if indegree[v] == 0:
queue.append(v)
return result if len(result) == n else [] # 空列表表示有环
# 示例调用
edges = [(6,1), (1,4), (4,3), (3,2), (2,5)] # 简化示例边
print("拓扑序列:", topological_sort(6, edges)) # 输出可能包含孤立点
使用深度优先搜索(DFS)实现拓扑排序的核心思想是:在对有向无环图进行 DFS 遍历时,每当一个顶点的所有邻接点都被访问完毕后,将其加入结果栈中。最后将栈逆序输出,即可得到拓扑序列。
这种方法得到的是逆拓扑序列的反向积累,因此最终需要反转栈的结果或从尾到头读取。
✅ 算法步骤(基于 DFS 的拓扑排序):
- 对图中每个未被访问的顶点调用 DFS;
- 在 DFS 过程中,先递归访问所有邻接顶点;
- 当该顶点的所有邻接点都处理完成后,将该顶点压入栈;
- 所有顶点遍历完成后,栈中元素从顶到底即为逆向拓扑序列;
- 弹出栈中所有元素,得到正向拓扑序列。
⚠️ 注意:此方法仅适用于 有向无环图(DAG)。若图中存在环,则无法生成有效的拓扑序列。
🧩 状态标记说明(检测环):
为了在 DFS 中检测是否存在环,需维护三种状态:
0:未访问1:正在访问(当前 DFS 路径上的节点)2:已访问且已完成回溯
若在 DFS 中遇到一个状态为 1 的节点,说明存在环。
✅ Python 实现代码:
def dfs_topological_sort(n, edges):
from collections import defaultdict
# 建图
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
visited = [0] * (n + 1) # 0:未访问, 1:搜索中, 2:已完成
stack = []
has_cycle = [False]
def dfs(u):
if visited[u] == 1: # 发现回溯边,存在环
has_cycle[True] = True
return
if visited[u] == 2:
return
visited[u] = 1 # 标记为正在访问
for v in graph[u]:
dfs(v)
visited[u] = 2 # 标记为已完成
stack.append(u) # 完成后入栈
for i in range(1, n + 1):
if visited[i] == 0:
dfs(i)
if has_cycle[0]:
break
if has_cycle[0]:
return [] # 存在环,无法拓扑排序
return stack[::-1] # 反转栈得拓扑序列
# 示例使用
edges = [(6,1), (1,4), (4,3), (3,2), (2,5)]
print("DFS 拓扑排序结果:", dfs_topological_sort(6, edges))
🔍 时间复杂度分析:
- 每个顶点和边只被访问一次 → $ O(n + e) $
- 空间复杂度:$ O(n + e) $(图存储 + 递归栈)
💡 示例解释:
假设图结构对应关系如下:
- 6 → 1 → 4 → 3 → 2 → 5
则 DFS 可能按退出顺序依次将5,2,3,4,1,6入栈,最终栈反转后为[6,1,4,3,2,5],符合示例中的拓扑序列。



被折叠的 条评论
为什么被折叠?



