如题

查课表接口java_查课表接口java

  • 解读题意:题目中的numCourses表示需要修读的课程总数,且课程编号是从0-numCourses-1的,prerequisites表示每两门课之间的依赖关系。我们需要给出各门课程学习的先后顺序,保证合理完成全部课程。如果无法完成所有课程,就返回空数组。
  • 什么时候无法完成
  • 当课程之间存在循环依赖的时候就无法完成,例如完成[1,2],[2,1]这样,因此我们可以将这些依赖关系抽象成一个有向图,需要做的就是检测图中有没有环
  • 如果可以完成,如何记录下来先后顺序
  • 将所有课程依赖关系看成图,那么完成课程的顺序就是这个有向图的拓扑排序顺序,而拓扑排序就是一次遍历每个入度为0的结点,入度为0即不被任何结点指向。
  • 有向图的拓扑排序可以利用DFS深度优先搜索或者BFS广度优先搜索进行,这里我们是用DFS
  • 对于DFS来说,图的拓扑排序可以理解为后序遍历的结果然后反转。因为后序遍历先遍历叶子节点,叶子节点其实就是出度为0的结点,否则就不是叶子,也就是说一次遍历出度为翻0的所有借点,反转后就是根据入度为0的顺序遍历了。如下:
public void traverse(List<Integer>[] graph,int s){
    for (Integer child : graph[s]) {
        traverse(graph,child);
    }
    arrayList.add(s);//在遍历代码后的位置将当前结点加入集合中,这里就是后序遍历
}
  • 注意:不能是先序遍历,因为有可能一个父母指向同一个孩子,此时先序遍历会直接遍历一个父母后就遍历孩子,此时该孩子还被另一个父结点指向,入度为1,遍历错误
  • 代码如下
class Solution {
    boolean[] visited;//用来记录遍历过的结点
    boolean Circle = false;//如果有环就将它置为true
    boolean[] onPath;//用来判断是否存在环
    ArrayList<Integer> arrayList;//用来记录后序遍历的每个结点
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        int[] res = new int[numCourses];//用来返回最后的结果
        arrayList = new ArrayList<>(numCourses);
        visited = new boolean[numCourses];
        //visited数组此处不用来判断是否存在环,而是因为我们无法保证图联通,需要将所有节点依次作为
        // 开始结点遍历图,此时如果依次遍历abc,下一次以b开始又会遍历bc,这就出现了重复遍历
        //visited数组可以避免重复遍历的问题。
        onPath = new boolean[numCourses];
        //因此使用onPath记录 当前 走过的路径,每次遍历到新结点时判断onPath是否为true,如果是
        //就代表之前遍历过这个结点,因此存在环,然后将Circle置为true。
        List<Integer>[] graph = buildGraph(numCourses, prerequisites);
        for (int i = 0; i < numCourses; i++) {
            traverse(graph,i);
        }
        if (Circle)return new int[]{};
        Collections.reverse(arrayList);
        for (int i = 0; i < arrayList.size(); i++) {
            res[i] = arrayList.get(i);
        }
        return res;
    }
    //建图,使用链表法,每个结点作为开始结点存在在List数组中,后面依次连接着它指向的结点
    public List<Integer>[] buildGraph(int numCourses,int[][] prerequisites){
        ArrayList<Integer>[] res = new ArrayList[numCourses];
        for (int i = 0; i < res.length; i++) {
            res[i] = new ArrayList<>();
        }
        for (int[] p : prerequisites){
            res[p[1]].add(p[0]);
            //由于学习p[0]之前需要学习p[1],因此由p[1]指向p[0]
        }
        return res;
    }
    //遍历图判断是否有环,并且记录后序遍历的结点
    public void traverse(List<Integer>[] graph,int s){
        if (onPath[s]){
            Circle = true;
            //不是return,而是用全局变量记录,因为可能只是一个分支有环,return只是让当前分支不再继续,
            //此时我们应该让其它分支也不用再继续遍历。

            //要注意,我们的onPath数组需要在退出的时候清除标记,即只记录当前路径,因为如果和visited
            // 一样的话,就无法判断是有环而重复还是从子节点开始遍历而重复了,因为我们无法判断图是否连通,
            // 需要将所有结点遍历,如果遍历到了子节点那么也是ture,因此onPath只记录当前遍历的路径。

            //注意:这里必须将判断环写在头而不是下面的visited写在头,因为我们只是让visited起到避免
            //重复遍历子节点的问题,但他仍然是可以在一次遍历中检测环的,这是它的本职工作,我们只是不用罢了。
            //如果将visited放在头部,那么碰到环还是可以被他检测出来,那么它无法分辨到底是重复遍历子节点导致
            //条件成立还是因为环导致条件成立。
        }
        if(visited[s] || Circle){
            return;
        }
        visited[s] = true;
        onPath[s] = true;
        for (Integer child : graph[s]) {
            traverse(graph,child);
        }
        arrayList.add(s);//将当前结点加入集合中
        onPath[s] = false;
    }
}