环检测及拓扑排序算法

引用:labuladong的算法小抄 (opens new window)

# 算法思路(DFS)

把问题转化成「有向图」这种数据结构,只要图中存在环,那就说明存在循环依赖。

回溯算法的「做选择」和「撤销选择」在 for 循环里面,而 DFS 对 onPath 数组的操作在 for 循环外面。

回溯算法和 DFS 算法的区别所在:回溯算法关注的是树枝, DFS 关注的是节点。 将图后序遍历的结果进行反转,就是拓扑排序的结果。

# 代码实现

class Solution {
    boolean[] visited;
    boolean[] onPath;
    boolean hasCycle = false;
    List<Integer> postorder = new ArrayList();
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        // 将问题转换为图结构
        List<Integer>[] graph = buildGrapth(numCourses,prerequisites);
        // visited避免DFS走重复路线
        visited = new boolean[numCourses];
        // onPath判断是否成环
        onPath = new boolean[numCourses];
        // 并不一定是连通图,要从每个节点都尝试遍历一次
        for(int i = 0; i < numCourses; i++){
            traverse(graph, i);
        }
        if(hasCycle){
            return new int[]{};
        } else {
            Collections.reverse(postorder);
            // 图后序遍历的结果反转,就是拓扑排序的结果
            int[] res = new int[numCourses];
            for(int i = 0; i < numCourses; i++){
                res[i] = postorder.get(i);
            }
            return res;
        }
    }

    List<Integer>[] buildGrapth(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){
            int from = edge[1];
            int to = edge[0];
            graph[from].add(to);
        }
        return graph;
    }

    void traverse(List<Integer>[] graph, int node){
        if(onPath[node]){
            hasCycle = true;
        }
        if(visited[node] || hasCycle){
            return;
        }
        visited[node] = true;
        onPath[node] = true;
        for(int dest : graph[node]){
            traverse(graph, dest);
        }
        postorder.add(node);
        onPath[node] = false;
    }
}