2021-01-12 207.课程表[拓扑排序]

207.课程表

这时候你有可能会问“不是1203项目管理吗?”你怎么偷梁换柱?
实际上emmm,不妨让我们先熟悉熟悉拓扑排序。

思路一:拓扑排序

建议看官方解答的动图
在这里插入图片描述
此时,根据拓扑排序的结果,0是[1,4,7]的先决条件,但是[1,4,7]没有顺序关系。

应用场景:

如课程表的安排
在这里插入图片描述

重要结论:

  1. 只有有向无环图才有拓扑排序(否则会有相互依赖)
  2. 拓扑排序的结果不唯一。

具体实践1:BFS

具体思想是:

  • while queue!=Empty:
    • 用入度来描述每一门课程,入度为0表示这门课其先修课程已经修完(包含最最基础课程无先修课程的情况)
    • 如果度数为0:
      • 将其出列,加入的答案res中
      • 每次将BFS队列中的一个元素出队列之后,修改其相关课程的入度情况。
  • 比较输出的res长度和所有课程数量,如果相等,则是正常的,如果res数量小,说明存在环,则不存在拓扑排序。
  • 一个技巧就是用LinkedList的方式实现图,用int[]的方式实现每个节点的编号
  • 这样每个节点删除的时候就可以用遍历其LinkedList元素个数的方式把与其相连的点的入度-1。
class Solution {
    
    
    List<List<Integer>> edges;
    int[] indeg; // 用来统计每个点的入度

    public boolean canFinish(int numCourses, int[][] prerequisites) {
    
    
	    // 第一步:创建每个节点(课程)对应的edge,edge[i]是一个存储它后续课程的ArrayList
	    // 这样子,每当我们需要跟新的时候,我们只要对edge[i]中的每个元素的indeg--即可。
        edges = new ArrayList<List<Integer>>();
        for (int i = 0; i < numCourses; ++i) {
    
    
            edges.add(new ArrayList<Integer>());
        }
       	// 第二步:统计每个节点的indegree。
        indeg = new int[numCourses];
        for (int[] info : prerequisites) {
    
    
            edges.get(info[1]).add(info[0]);
            ++indeg[info[0]]; //因为info[0]有一个先修课程info[1]
        }

		// 第三步:BFS的初始化,将所有没有先修课程的添加到queue中。
        Queue<Integer> queue = new LinkedList<Integer>();
        for (int i = 0; i < numCourses; ++i) {
    
    
            if (indeg[i] == 0) {
    
    
                queue.offer(i);
            }
        }
		
		// 第四步:将所有元素一次出队,并更新他们的indegree。
		// visited表示我们目前可以安排的课程数量
        int visited = 0;
        while (!queue.isEmpty()) {
    
    
            ++visited;
            int u = queue.poll();
            for (int v: edges.get(u)) {
    
    
                --indeg[v];
                if (indeg[v] == 0) {
    
    
                    queue.offer(v);
                }
            }
        }
		
		// 当我们目前可以安排的课程数量等于总课程数量,说明没有环,返回True。
        return visited == numCourses;
    }
}

具体实践2:DFS

我们首先遍历最高级的课程,它的特点是出度为0。(出度的意思是当前元素被后面多少个元素需要)
我们利用DFS,每次将一个分支的课程从高级到低级一起添加到一个栈里面,然后倒序输出就是我们要的结果了。

对于图中的任意一个节点,它只可能有三种状态,即

  • 未搜索:我们还没有搜索到这个节点
  • 搜索中:它还没有出DFS搜索的栈Pile(出去的条件是当前节点的出度为0,说明它是最高级的课程了)如果在搜索过程中发现下一个元素也在搜索中,则出现了一个环(互相依赖),我们就返回False,即不存在拓扑排序结果。
  • 已完成:我们添加到用于保存结果的栈中。

官方解答和上述描述的算法不太一样,而是根据题目特点,直接检查是否有环,如果有环就返回false。

class Solution {
    
    
    List<List<Integer>> edges;
    int[] visited; // 用来记录每个元素的状态,0是为搜索,1是搜索中,2是搜索完成
    boolean valid = true;

    public boolean canFinish(int numCourses, int[][] prerequisites) {
    
    
        edges = new ArrayList<List<Integer>>();
        for (int i = 0; i < numCourses; ++i) {
    
    
            edges.add(new ArrayList<Integer>());
        }
        // 初始化所有的边,这里边[i,j]表示i是j的后续课程。
        visited = new int[numCourses];
        for (int[] info : prerequisites) {
    
    
            edges.get(info[1]).add(info[0]);
        }
        // 如果检测到一个环就直接跳出去
        for (int i = 0; i < numCourses && valid; ++i) {
    
    
            if (visited[i] == 0) {
    
    
                dfs(i);
            }
        }
        return valid;
    }

    public void dfs(int u) {
    
    
    	// 因为dfs的条件是当前元素没有出度,所以它一定是
        visited[u] = 1;
        // 检测v的上层元素
        for (int v: edges.get(u)) {
    
    
	        // 如果是visited[v]==2,即已经完成搜索了
            if (visited[v] == 0) {
    
    
                dfs(v); //如果上一层
                if (!valid) {
    
    
                    return;
                }
            }
            // 如果检测到一个环 
            else if (visited[v] == 1) {
    
    
                valid = false;
                return;
            }
        }
        visited[u] = 2;
    }
}

收获:

  1. 对于int [][] a可以用for(int [] tmp : a)的方式调用其内部元素。
  2. 对于List<List<Integer>> tmp,我们初始化的时候:tmp = new ArrayList<List<Integer>>()然后,tmp.add(new ArrayList<Integer>())。即List的具体实现为ArrayList。

自己尝试复现代码

BFS

class Solution {
    
    
    List<List<Integer>> edges;
    int[] indeg;

    public boolean canFinish(int numCourses, int[][] prerequisites) {
    
    
        // 第一步:初始化+读取每一条边
        edges = new ArrayList<List<Integer>>();
        for(int i=0;i<numCourses;i++)
            edges.add(new ArrayList<Integer>());
        
        

        // 第二步:初始化edges+indeg
        indeg = new int[numCourses];
        for(int[] courses : prerequisites){
    
    
            // 添加某一个课程classes[1]的后续课程
            edges.get(courses[1]).add(courses[0]);
            indeg[courses[0]]++;
        }

        // 第三步:初始化bfs
        Queue<Integer> q = new LinkedList<>();
        for(int i=0;i<numCourses;i++)
            if(indeg[i]==0)
                q.offer(i);

        // 第四步:bfs
        int visited = 0; //用来记录目前我们已经安排的课程
        while(!q.isEmpty()){
    
    
            int crtcourse = q.poll();
            for(int latterCourse: edges.get(crtcourse)){
    
    
                indeg[latterCourse]--;
                // 如果当前课程为0,则我们可以添加到队列中了(可以被安排)
                if(indeg[latterCourse]==0)
                    q.offer(latterCourse);
            }
            visited++;
        }

        return visited==numCourses;
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_44495738/article/details/112546613