如何判断有向图有没有环?leetcode 力扣 207 课程表

152 阅读2分钟

方法一 深度优先遍历

第一步,使用邻接表构建有向图

lc207_1.jpeg

        List<List<Integer>> adjacency = new ArrayList<>();
        
        //必须为每个表项创建空List,否则dfs里遍历邻接表会抛出越界异常
        //注意题目要求,你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 
        for(int i = 0; i < numCourses; i++){
            adjacency.add(new ArrayList<>());
        }

        for(int[] pair : prerequisites){
            adjacency.get(pair[1]).add(pair[0]);
        }

第二步,使用flags数组对图节点进行标记,0表示未遍历过该节点,1表示当前dfs已经遍历过该节点,-1表示过去的dfs已经遍历过该节点

第三步,构建dfs

private boolean dfs(List<List<Integer>> adjacency, int[] flags, int courseIndex) {
        if (flags[courseIndex] == 1) {
            return false;
        }

        if (flags[courseIndex] == -1) {
            return true;
        }

        flags[courseIndex] = 1;

        for (int nextCourse : adjacency.get(courseIndex)) {
            if (!dfs(adjacency, flags, nextCourse)) {
                return false;
            }
        }

        flags[courseIndex] = -1;
        return true;
    }

递归出口

  • 如果当前节点的flag == 1,说明有向图存在环,直接return false
  • 如果当前节点的flag == -1,说明该节点在之前的dfs已经遍历过,无需继续遍历

递归前将当前节点标记为1
使用for循环依次取出当前节点邻接表里的节点,继续dfs
递归后,因为return true,将当前节点标记为-1

第四步,使用for循环从有向图里每个节点开始dfs

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        List<List<Integer>> adjacency = new ArrayList<>();
        for(int i = 0; i < numCourses; i++){
            adjacency.add(new ArrayList<>());
        }

        for(int[] pair : prerequisites){
            adjacency.get(pair[1]).add(pair[0]);
        }

        int[] flags = new int[numCourses];
        
        for(int i = 0; i < numCourses; i++){
            if(!dfs(adjacency, flags, i)){
                return false;
            }
        }

        return true;
    }

    private boolean dfs(List<List<Integer>> adjacency, int[] flags, int courseIndex) {
        if (flags[courseIndex] == 1) {
            return false;
        }

        if (flags[courseIndex] == -1) {
            return true;
        }

        flags[courseIndex] = 1;

        for (int nextCourse : adjacency.get(courseIndex)) {
            if (!dfs(adjacency, flags, nextCourse)) {
                return false;
            }
        }

        flags[courseIndex] = -1;
        return true;
    }
}

方法二 广度优先遍历(算法复杂,不推荐)

算法思路
总的来说,就是看能不能把所有节点的入度变成0

  • 首先构建好邻接表和入度表
  • 入度为0的节点入队列
  • 从队列中取出一个入度为0的节点,总节点数量-1,将该节点的邻接节点,对应入度表的入度-1,如果该节点入度变为0,则入队
  • 循环该操作,如果总节点数量 == 0,则没有环

lc207_2.jpeg

lc207_3.jpeg

lc207_4.jpeg

lc207_5.jpeg

lc207_6.jpeg

lc207_7.jpeg

lc207_8.jpeg

lc207_9.jpeg

lc207_10.jpeg

lc207_11.jpeg

lc207_12.jpeg

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        List<List<Integer>> adjacency = new ArrayList<>();
        for (int i = 0; i < numCourses; i++) {
            adjacency.add(new ArrayList<>());
        }

        int[] indegree = new int[numCourses];

        for (int[] pair : prerequisites) {
            adjacency.get(pair[1]).add(pair[0]);

            ++indegree[pair[0]];
        }

        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < numCourses; i++) {
            if (indegree[i] == 0) {
                queue.offer(i);
            }
        }

        while (!queue.isEmpty()) {
            int pre = queue.poll();
            numCourses--;
            for (int next : adjacency.get(pre)) {
                if (--indegree[next] == 0) {
                    queue.offer(next);
                }
            }
        }

        return numCourses == 0;
    }
}