加载中…
应用场景:任务依赖 · 编译顺序 · 课程表
交互式动画演示,可调整参数并单步执行。
全屏打开拓扑排序(Topological Sort) 对有向无环图(DAG)的顶点排成线性序列,使得对每条有向边 (u, v),u 都排在 v 前面。
典型应用:课程先修关系、编译依赖、任务调度。
graph LR A[数据结构] --> C[操作系统] B[离散数学] --> C C --> D[编译原理] A --> D D --> E[毕业设计]
合法拓扑序之一:数据结构 → 离散数学 → 操作系统 → 编译原理 → 毕业设计
前提:图必须是 DAG(无环)。有环则不存在拓扑序。
不断删除入度为 0 的节点:
public int[] topologicalSort(int n, int[][] edges) {
List<Integer>[] graph = new ArrayList[n];
int[] inDegree = new int[n];
for (int i = 0; i < n; i++) graph[i] = new ArrayList<>();
for (int[] e : edges) {
graph[e[0]].add(e[1]);
inDegree[e[1]]++;
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) queue.offer(i);
}
int[] order = new int[n];
int idx = 0;
while (!queue.isEmpty()) {
int u = queue.poll();
order[idx++] = u;
for (int v : graph[u]) {
inDegree[v]--;
if (inDegree[v] == 0) queue.offer(v);
}
}
// idx < n 说明有环
return idx == n ? order : new int[0];
}DFS 完成时记录节点,最后反转:
public int[] topologicalSortDFS(int n, int[][] edges) {
List<Integer>[] graph = new ArrayList[n];
for (int i = 0; i < n; i++) graph[i] = new ArrayList<>();
for (int[] e : edges) graph[e[0]].add(e[1]);
int[] state = new int[n]; // 0=未访问, 1=访问中, 2=已完成
Deque<Integer> stack = new ArrayDeque<>();
boolean[] hasCycle = {false};
for (int i = 0; i < n; i++) {
if (state[i] == 0) {
dfs(graph, i, state, stack, hasCycle);
}
}
if (hasCycle[0]) return new int[0];
int[] order = new int[n];
for (int i = 0; i < n; i++) order[i] = stack.pop();
return order;
}
private void dfs(List<Integer>[] graph, int u, int[] state,
Deque<Integer> stack, boolean[] hasCycle) {
state[u] = 1;
for (int v : graph[u]) {
if (state[v] == 1) { hasCycle[0] = true; return; }
if (state[v] == 0) dfs(graph, v, state, stack, hasCycle);
}
state[u] = 2;
stack.push(u);
}| 方法 | 判断依据 |
|---|---|
| BFS(Kahn) | 处理完的节点数 < n → 有环 |
| DFS | 遇到"访问中"的节点 → 有环 |
问题:n 门课程,prerequisites[i] = [a, b] 表示学 a 前必须先学 b。能否完成所有课程?
public boolean canFinish(int numCourses, int[][] prerequisites) {
return topologicalSort(numCourses, prerequisites).length == numCourses;
}从排序的外星词表中推导字母顺序 → 建图 + 拓扑排序。
任务 A 依赖 B、C → 边 B→A, C→A
拓扑序 = 合法执行顺序
在 DAG 上按拓扑序做动态规划:
问题:DAG 中的最长路径。
public int longestPath(int n, int[][] edges, int[] weight) {
List<int[]>[] graph = new ArrayList[n];
int[] inDegree = new int[n];
for (int i = 0; i < n; i++) graph[i] = new ArrayList<>();
for (int[] e : edges) {
graph[e[0]].add(new int[]{e[1], e[2]});
inDegree[e[1]]++;
}
Queue<Integer> queue = new LinkedList<>();
int[] dist = new int[n];
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) queue.offer(i);
}
while (!queue.isEmpty()) {
int u = queue.poll();
for (int[] edge : graph[u]) {
int v = edge[0], w = edge[1];
dist[v] = Math.max(dist[v], dist[u] + w);
if (--inDegree[v] == 0) queue.offer(v);
}
}
return Arrays.stream(dist).max().getAsInt();
}| 维度 | BFS(Kahn) | DFS |
|---|---|---|
| 实现 | 直观(入度) | 递归(后序) |
| 环检测 | 计数 < n | 三色标记 |
| 多解 | 可用优先队列得字典序最小 | 取决于遍历顺序 |
| 适用 | 更常用 | 需要 DFS 序时 |
[a, b] 表示 b→a(先修 b 才能学 a),别搞反。idx < n 就是有环。PriorityQueue 替代 Queue。