算法知识图谱总览 / 图基础与遍历 / 拓扑排序

拓扑排序

P0basicdeepG-B-1

What · 是什么

DAG节点线性排序,Kahn算法(入度BFS)或DFS逆序O(V+E)

拓扑排序将DAG(有向无环图)的所有节点排成线性序列,使得对任意有向边(u,v),u在序列中位于v之前。常用Kahn算法(基于入度的BFS)或DFS后序逆序实现。拓扑排序不唯一。若图有环则不存在拓扑序。AOV网(顶点活动网)的拓扑排序可用于课程安排、任务调度等。AOE网可在此基础上求关键路径。

Why · 为什么

直觉 · Intuition

排课表:学数据结构前必须学离散数学,学编译技术前必须学算法语言。将所有课程按依赖关系排序,这就是拓扑排序。

原理 · Principle

Kahn算法:维护入度为0的节点集合,每次取出一个放入结果序列,删除其所有出边,将新入度为0的节点加入集合。DFS算法:对所有未访问节点DFS,完成时逆序记录(后序逆序=拓扑序)。

历史 · History

Kahn算法由Arthur Kahn于1962年提出。DFS方法利用了'完成时间逆序即拓扑序'的性质。两者时间复杂度均为O(V+E)。

How · 实现

伪代码

Kahn:
  S ← {nodes with indeg=0}
  L ← []
  while S not empty:
    remove u from S, append to L
    for each edge (u,v):
      indeg[v] -= 1
      if indeg[v]==0: add v to S
  if len(L)<n: has cycle
  return L

Python 实现

from collections import deque
import sys

def topo_sort_kahn(n, edges):
    """Kahn's algorithm (BFS-based). Returns topological order or None if cycle."""
    adj = [[] for _ in range(n)]
    indeg = [0] * n
    for u, v in edges:
        adj[u].append(v)
        indeg[v] += 1
    q = deque(i for i in range(n) if indeg[i] == 0)
    order = []
    while q:
        u = q.popleft()
        order.append(u)
        for v in adj[u]:
            indeg[v] -= 1
            if indeg[v] == 0:
                q.append(v)
    return order if len(order) == n else None

def topo_sort_dfs(n, edges):
    """DFS-based topological sort. Returns order or None if cycle."""
    adj = [[] for _ in range(n)]
    for u, v in edges:
        adj[u].append(v)
    UNVISITED, VISITING, DONE = 0, 1, 2
    state = [UNVISITED] * n
    order = []
    has_cycle = [False]
    def dfs(u):
        state[u] = VISITING
        for v in adj[u]:
            if state[v] == VISITING:
                has_cycle[0] = True
                return
            if state[v] == UNVISITED:
                dfs(v)
                if has_cycle[0]:
                    return
        state[u] = DONE
        order.append(u)
    for i in range(n):
        if state[i] == UNVISITED:
            dfs(i)
            if has_cycle[0]:
                return None
    order.reverse()
    return order

if __name__ == "__main__":
    edges = [(0, 1), (0, 2), (1, 3), (2, 3)]
    order = topo_sort_kahn(4, edges)
    assert order is not None
    assert order.index(0) < order.index(1)
    assert order.index(0) < order.index(2)
    assert order.index(1) < order.index(3)
    assert order.index(2) < order.index(3)
    order2 = topo_sort_dfs(4, edges)
    assert order2 is not None
    assert order2.index(0) < order2.index(1)
    # Cycle detection
    cycle_edges = [(0, 1), (1, 0)]
    assert topo_sort_kahn(2, cycle_edges) is None
    assert topo_sort_dfs(2, cycle_edges) is None
    print("topo-sort: ALL TESTS PASSED")
PyPy 提示Kahn用deque.popleft()在PyPy上高效。DFS递归注意栈深度。大图用邻接表比邻接矩阵快。
时间复杂度O(V+E)
空间复杂度O(V+E)
注意 · 局限仅适用于DAG。有环图无法拓扑排序。拓扑序不唯一(取决于入度为0的节点取出顺序)。

易错点

例题

OJ题号难度考点链接
LeetCode2073DAG 是否有环判定https://leetcode.cn/problems/course-schedule/
LeetCode2104求拓扑排序序列https://leetcode.cn/problems/course-schedule-ii/
LuoguP40174拓扑排序+DP 统计最大食物链数量https://www.luogu.com.cn/problem/P4017
步骤迷你图(节点 = 算法步骤 · 边 = 流程 · 绿色 = 起始步骤)
上游 · 本知识点依赖(前置/变体/组合/共现)
下游 · 依赖本知识点

信源

✓ = 已核验 · △ = 未核验