Leetcode 207. Course Schedule

来源:互联网 发布:奇异值分解 svd逆矩阵 编辑:程序博客网 时间:2024/05/29 13:23

There are a total of n courses you have to take, labeled from 0 to n - 1.

Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]

Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?

For example:

2, [[1,0]]
There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible.

2, [[1,0],[0,1]]
There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible.

s思路:
1. 图。描述两者之间关系用edge表示,每个对象就是一个node。首先,把问题转化成图。
2. 看到图,心里就打颤,遇到的少,心里没底。也不是没底,是深不可侧,不见底。所以,需要先把心安好,既重视,更要看轻图,才能学好图。
3. 回到图。先转换图,把每门课的所有先修课程和这门课连接起来,然后做dfs遍历,看是否cycle,用dfs时,就要对每个节点是否visited做标记,而且dfs由于是recursive,和其他所有recursive一样,都有层级之分。在dfs的visited数据结构,需要有三个状态,不同与之前见过的两个状态表示是否访问。三个状态分别是:没访问,正在访问但没访问完成,以及访问完成。“正在访问但没访问完成”:表示从这个节点出发进入下一个层次去访问,此时这个点确实被访问了,但是这个点属于现在正在访问的path的一个节点;“访问完成”表示当前点的下一个层次的遍历以及完成,注意,下一个层次包括所有邻接节点都访问了,这个点才算访问完。
4. 写完dfs,发现图的dfs的套路和backtracking一毛一样,都是for+recursive。不同的就是,visited有3中状态!
5. 按理说,能用dfs的也可以用bfs,只要能想办法添加一些辅助量。图的bfs和其他的bfs一样,也是一层访问完再访问下一层,而且也用到queue。如果说dfs检查是否有cycle这个操作就可以表面是否所有课都可以选,那么bfs需要做什么呢?看了答案,很有意思,仍然是检查是否有cycle,但需利用图论的知识:对每个节点的入度(indegree)统计,然后找到入度为0的所有点放在queue里,每次取出一个点,找到和其相连的所有节点,然后把这些节点的入度减一,表示删除这条边,如果某一次删除,导致入度变成0,那么就把这个点加入queue,最后等queue为空后,再看所有点的入度是否全为0,如果全为0表示没有cycle,否则有cycle。
6. 数学原因先不说。单说思路,仍然是找到边界这个方法的应用。先统计所有入度,从入度为0的点入手,这里入度为0的点就是边界。形象的说,一个图的入读为0的点在图上也一定是在边缘,只出不进,所以是边缘。值得注意的是,如果没有入度为0的点,说明这个图的所有点都在loop内;然后删除和这个点相连的节点之间的连接,同时入度减一,如果没有cycle,最后一定可以把所有所有的入度变成0。所以,根据这个条件,就可以判断是否有cycle了!
7. 比较一下dfs和bfs区别:dfs的过程是在模拟如何去遍历这个图的全过程;bfs则是模拟如何从图的边缘一步一步的把图给“拆”掉,如果把能拆的都拆了,发现还有连线,那就是有cycle。一个是在沿路走;一个是在把正常的路拆了看留下啥!

//方法1:recursive. dfs,使用visited表示已经访问。class Solution {public:    bool dfs(vector<vector<int>>&graph,vector<int>&visited,int i){        //dfs:这个和backtracking套路一样        if(visited[i]==2) return true;//完全访问过        if(visited[i]==1) return false;//正在访问,所以再次遇到表示有cycle        visited[i]=1;//第一次访问,所以置为1        for(int j=0;j<graph[i].size();j++){            if(!dfs(graph,visited,graph[i][j])) return false;           }        visited[i]=2;        return true;    }    bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {        //        vector<vector<int>> graph(numCourses);        vector<int> visited(numCourses,0);        //step 1: build graph        for(auto p:prerequisites){            graph[p.first].push_back(p.second);            }        //step 2: dfs找cycle        for(int i=0;i<numCourses;i++){            if(!dfs(graph,visited,i))                return false;        }        return true;    }};//方法2:iterative,queue,统计入度class Solution {public:    bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {        //        vector<vector<int>> graph(numCourses);        vector<int> indegree(numCourses,0);        queue<int> qq;        //step 1: build graph        for(auto p:prerequisites){            graph[p.first].push_back(p.second);                indegree[p.second]++;        }        //step 2:找图边缘入度为0的点放入queue        for(int i=0;i<numCourses;i++){            if(indegree[i]==0)                qq.push(i);            }        //step 3:拆掉和入度零连接的线,入度减少        while(!qq.empty()){            int cur=qq.front();            qq.pop();            for(auto k:graph[cur]){                indegree[k]--;                if(indegree[k]==0) qq.push(k);              }           }        //step 4:看是否入度都为0        for(int i=0;i<numCourses;i++){            if(indegree[i]!=0)                return false;          }        return true;    }};
0 0