环检测常用于判定关系中是否拥有死锁关系

死锁:当两个以上的运算单元,双方都在等待对方停止执行,以获取系统资源,但是没有一方提前退出时,就称为死锁

而拓扑排序则是在检测无环基础上,安排已知具体先后关系的各关系

在排课中,可能会遇到一些课对于另一些课是先修课:比如数据库的先修课是数据结构,数据结构(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; // 返回邻接表
    }
}