n门课程的编号为0~n-1。输入一个数组prerequisites,它的每个元素prerequisites[i]表示两门课程的先修顺序。如果prerequisites[i]=[ai,bi],那么必须先修完bi才能修ai。请根据总课程数n和表示先修顺序的prerequisites得出一个可行的修课序列。如果有多个可行的修课序列,则输出任意一个可行的序列;如果没有可行的修课序列,则输出空序列。
例如,总共有4门课程,先修顺序prerequisites为[[1,0],[2,0],[3,1],[3,2]],一个可行的修课序列是0→2→1→3。
先根据先修顺序构建出有向图graph,graph用一个HashMap表示邻接表,它的键是先修课程,它的值是必须在键对应的课程之后学习的所有课程。同时,将每个节点的入度保存到数组inDegrees中,“inDegrees[i]”表示节点i的入度。
接下来用广度优先搜索算法实现拓扑排序。队列中保存的是入度为0的节点。每次从队列中取出一个节点,将该节点添加到拓扑排序序列中,然后找到该课程的后修课程并将它们的节点的入度减1,这相当于删除从先修课程到后修课程的边。如果发现新的入度为0的节点,则将其添加到队列中。重复这个过程直到队列为空,此时要么图中所有节点都已经访问完毕,已经得到了完整的拓扑排序序列;要么剩下的还没有搜索到的节点形成一个环,已经不存在入度为0的节点。
public class Test {
public static void main(String[] args) {
int[][] prerequisites = {{1, 0}, {2, 0}, {3, 1}, {3, 2}};
int[] result = findOrder(4, prerequisites);
for (int item : result) {
System.out.println(item);
}
}
public static int[] findOrder(int numCourses, int[][] prerequisites) {
// graph用一个HashMap表示邻接表,它的键是先修课程,它的值是必须在键对应的课程之后学习的所有课程
Map<Integer, List<Integer>> graph = new HashMap<>();
for (int i = 0; i < numCourses; i++) {
graph.put(i, new LinkedList<>());
}
// 表示节点i的入度
int[] inDegrees = new int[numCourses];
for (int[] prereq : prerequisites) {
graph.get(prereq[1]).add(prereq[0]);
inDegrees[prereq[0]]++;
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (inDegrees[i] == 0) {
queue.add(i);
}
}
List<Integer> order = new LinkedList<>();
while (!queue.isEmpty()) {
int course = queue.remove();
order.add(course);
for (int next : graph.get(course)) {
inDegrees[next]--;
if (inDegrees[next] == 0) {
queue.add(next);
}
}
}
return order.size() == numCourses ? order.stream().mapToInt(i -> i).toArray() : new int[0];
}
}