概述
拓扑排序是一种线性排序,它用于对有向无环图(DAG,Directed Acyclic Graph)中的节点进行排序,使得对于每一条有向边(u, v)
,节点u
在节点v
之前。拓扑排序的应用场景通常是任务调度、编译依赖解析等。
例子
假设我们有一个课程表,其中一些课程有先修课程的要求。我们需要按照一个合理的顺序安排这些课程,确保每门课程的先修课程在它之前被学习。
问题描述
给定课程数目numCourses
和一个数组prerequisites
,其中prerequisites[i] = [a, b]
表示要学习课程a
,你必须先学习课程b
。返回一个可以完成所有课程的顺序。如果存在循环依赖,返回空数组。
解决方案
-
图的表示:
- 使用邻接表表示课程依赖关系。
- 使用一个入度数组
inDegree
来记录每个节点的入度。
-
初始化:
- 遍历
prerequisites
,构建邻接表adjList
和入度数组inDegree
。
- 遍历
-
找到入度为0的节点:
- 将所有入度为0的节点加入队列,这些节点可以作为课程学习的起点。
-
BFS遍历图:
- 逐个从队列中取出节点,将其加入结果列表。
- 对于当前节点的每一个邻居节点,将其入度减1,如果入度变为0,则将该节点加入队列。
-
检查循环依赖:
- 如果遍历完成后,结果列表中的节点数量不等于
numCourses
,则存在循环依赖,返回空数组。
- 如果遍历完成后,结果列表中的节点数量不等于
代码
import java.util.*;
public class CourseSchedule {
public int[] findOrder(int numCourses, int[][] prerequisites) {
List<List<Integer>> adjList = new ArrayList<>();
int[] inDegree = new int[numCourses];
for (int i = 0; i < numCourses; i++) {
adjList.add(new ArrayList<>());
}
// 构建图和入度数组
for (int[] prerequisite : prerequisites) {
int course = prerequisite[0];
int preCourse = prerequisite[1];
adjList.get(preCourse).add(course);
inDegree[course]++;
}
// 找到所有入度为0的节点
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (inDegree[i] == 0) {
queue.offer(i);
}
}
int[] order = new int[numCourses];
int index = 0;
// BFS遍历
while (!queue.isEmpty()) {
int current = queue.poll();
order[index++] = current;
for (int neighbor : adjList.get(current)) {
inDegree[neighbor]--;
if (inDegree[neighbor] == 0) {
queue.offer(neighbor);
}
}
}
// 如果结果中的课程数量不等于numCourses,说明存在循环依赖
if (index != numCourses) {
return new int[0];
}
return order;
}
public static void main(String[] args) {
CourseSchedule cs = new CourseSchedule();
int numCourses = 4;
int[][] prerequisites = {{1, 0}, {2, 0}, {3, 1}, {3, 2}};
int[] order = cs.findOrder(numCourses, prerequisites);
System.out.println(Arrays.toString(order));
}
}
总结
拓扑排序通过将节点按依赖关系进行排序,有效地解决了类似课程表的依赖问题。通过BFS(或DFS)的方式,我们可以找到一个合法的课程学习顺序,确保所有先修课程都能在相关课程之前完成。如果存在循环依赖,则无法找到有效的学习顺序。