拓扑排序

什么是拓扑排序

拓扑排序(Topological Sorting)把有向无环图(DAG)的所有顶点排成一个线性序列,使得对每条边 u→v,u 都出现在 v 之前。它本质上是把图中的偏序关系变成全序

三个关键性质:

  • 只适用于 DAG:图里有环就不存在合法拓扑序(可以用它做环检测);
  • 结果不唯一:同一张图通常有多种合法拓扑序;
  • 语义明确:”u 排在 v 前”对应现实中的依赖关系——先修课、任务依赖、编译顺序。

典型应用:任务调度、课程安排、编译顺序(源文件依赖)、电路设计中的布线顺序、生产调度。

算法一:Kahn(基于入度,BFS)

反复”找出当前入度为 0 的节点”并移除:

  1. 统计所有节点的入度;
  2. 入度为 0 的节点入队;
  3. 出队节点加入结果序列,将其所有后继的入度减 1;
  4. 入度变为 0 的后继入队;
  5. 重复直到队列空。若结果节点数 ≠ 总节点数,说明图中有环
bool topologicalSort(vector<vector<int>>& G, vector<int>& inDegree, int n) {
    queue<int> q;
    vector<int> res;
    for (int i = 0; i < n; i++)
        if (inDegree[i] == 0) q.push(i);

    while (!q.empty()) {
        int u = q.front(); q.pop();
        res.push_back(u);
        for (int v : G[u])
            if (--inDegree[v] == 0) q.push(v);
    }
    return res.size() == n;   // false 说明有环
}

算法二:DFS 后序

对图做 DFS,递归返回后把当前节点压栈,最终栈的逆序就是拓扑序:

def topological_sort_dfs(graph):
    visited = set()
    stack = []

    def dfs(u):
        visited.add(u)
        for v in graph.get(u, []):
            if v not in visited:
                dfs(v)
        stack.append(u)      # 所有后继处理完才压栈

    for u in graph:
        if u not in visited:
            dfs(u)
    return stack[::-1]       # 逆序即拓扑序

复杂度

算法时间空间特点
KahnO(V+E)O(V)直观、适合动态图、天然支持环检测
DFSO(V+E)O(V)代码简洁,需要递归(可用三色标记判环)

实战:课程安排(LeetCode 207)

判断能否完成所有课程,即判断依赖图是否无环——Kahn 版:

public boolean canFinish(int numCourses, int[][] prerequisites) {
    List<Integer>[] graph = new List[numCourses];
    int[] inDegree = new int[numCourses];
    for (int i = 0; i < numCourses; i++) graph[i] = new ArrayList<>();
    for (int[] cp : prerequisites) {
        graph[cp[1]].add(cp[0]);
        inDegree[cp[0]]++;
    }
    Queue<Integer> q = new LinkedList<>();
    for (int i = 0; i < numCourses; i++)
        if (inDegree[i] == 0) q.offer(i);

    int count = 0;
    while (!q.isEmpty()) {
        int pre = q.poll();
        count++;
        for (int cur : graph[pre])
            if (--inDegree[cur] == 0) q.offer(cur);
    }
    return count == numCourses;
}

技巧与常见问题

  • 环检测:Kahn 看结果数量,DFS 用三色标记(灰 = 在递归栈中,遇到灰节点即环);
  • 并行任务:同一时刻入度为 0 的节点可以并行处理;
  • 字典序最小拓扑序:把 Kahn 的普通队列换成优先队列即可;
  • 输出具体课程顺序:LeetCode 210 要求返回顺序,Kahn 的 res 就是答案。
滚动至顶部