环检测常用于判定关系中是否拥有死锁关系
死锁:当两个以上的运算单元,双方都在等待对方停止执行,以获取系统资源,但是没有一方提前退出时,就称为死锁
而拓扑排序则是在检测无环基础上,安排已知具体先后关系的各关系
在排课中,可能会遇到一些课对于另一些课是先修课:比如数据库的先修课是数据结构,数据结构(Java)的先修课是Java,Java的先修课是C语言,我们如果需要排课,就首先得排除错误数据导致存在环,否则永远无法排下先修课,也就无法排出这条链上的所有课。(故加入bool数组判断是否成环,加入bool变量作成环标志)
如何把课排好?其实很简单,先把先修关系存到邻接表中,对于所有课,找出它的一条完整的先修链(加入bool数组判断是否已在先修链,已在已有先修链则无需再找),然后输出所有先修链即可。
class Solution {
boolean[] visited, onPath; // 定义两个布尔数组,用于记录每个课程是否被访问过和是否在当前路径上
boolean hasCycle; // 定义一个布尔变量,用于记录图中是否有环
List<Integer> res; // 定义一个列表,用于存储拓扑排序的结果
public int[] findOrder(int numCourses, int[][] prerequisites) {
res = new LinkedList<Integer>(); // 初始化结果列表
List<Integer>[] graph = buildGraph(numCourses, prerequisites); // 构建邻接表表示的有向图
visited = new boolean[numCourses]; // 初始化访问数组
onPath = new boolean[numCourses]; // 初始化路径数组
for (int i = 0; i < numCourses; i++) { // 遍历每个课程
traverse(i, graph); // 从每个课程开始深度优先搜索
}
Collections.reverse(res); // 反转结果列表,得到正确的拓扑排序
return hasCycle ? new int[0] : res.stream().mapToInt(Integer::intValue).toArray();// 如果有环,返回空数组,否则返回结果列表转换的int数组
}
private void traverse(int s, List<Integer>[] graph) {
if (onPath[s]) { // 如果当前课程已经在路径上,说明有环
hasCycle = true; // 设置hasCycle为true
return; // 结束递归
}
if (visited[s]) // 如果当前课程已经被访问过,直接返回
return;
visited[s] = true; // 标记当前课程为已访问
onPath[s] = true; // 标记当前课程为在路径上
for (int i : graph[s]) { // 遍历当前课程的后继课程
traverse(i, graph); // 递归搜索后继课程
}
res.add(s); // 将当前课程加入结果列表
onPath[s] = false; // 取消当前课程的路径标记
}
private List<Integer>[] buildGraph(int numCourses, int[][] prerequisites) {
List<Integer>[] graph = new LinkedList[numCourses]; // 创建一个列表数组,表示邻接表
for (int i = 0; i < numCourses; i++) { // 遍历每个课程
graph[i] = new LinkedList(); // 初始化每个列表为空
}
for (int[] edge : prerequisites) { // 遍历每个先决条件
graph[edge[1]].add(edge[0]); // 将边添加到邻接表中,表示edge[1]是edge[0]的先决条件
}
return graph; // 返回邻接表
}
}