# [207. Course Schedule](https://leetcode.com/problems/course-schedule/) # 思路 仔细分析题目可知这道题的本质就是在有向图中检测环。而在有向图中检测环又可以看作是拓扑排序(不懂拓扑排序的童鞋可[参考此处](https://www.jianshu.com/p/b59db381561a))的应用. 拓扑排序又有两种基本思路: BFS和DFS. ## BFS BFS对有向无环图(DAG, Directed Acyclic Graph)进行拓扑排序的基本思路是: 1. 从DAG中选择一个 没有前驱(即入度为0)的顶点并输出; 2. 从DAG中删除该顶点和所有以它为起点的有向边; 3. 重复 1 和 2 直到当前的 DAG 图为空。 稍加改动即可判断有向图中是否存在环: 1. 先将所有入度(in degree)为0的顶点入栈(或队列); 2. 从栈顶取出一个元素并用count自加计数, 将所有以其为起点的边的终点的入度减1, 此时若该终点入度为0则入栈; 3. 栈非空时, 重复2; 4. 最后判断count是否等于图的顶点数, 若不相等则说明有环. 为了方便找到以某个顶点为起点的所有边, 我们首先需要建一个二维数组G记录所有边, G[i]记录着以顶点i为起点的所有边的终点. DFS同. **时间复杂度分析:** 设图有n个顶点,e条边,在拓扑排序的过程中,搜索入度为零的顶点所需的时间是O(n)。 在正常情况下,每个顶点进一次栈,出一次栈,所需时间O(n)。每个顶点入度减1的运算共执行了e次(若图基于邻接表)。所以总的时间复杂为O(n+e)。 ## DFS 和常见的DFS一样, 我们需要一个一维数组 visited 来记录访问状态,但这里有三种状态: * 0 表示还未访问过, 需要进一步调用递归函数; * 1 表示已经访问了, 直接返回true即可; * -1 表示正在访问, 表示遇到了两次, 有环, 返回false。 时间复杂度同BFS. # C++ ## BFS ``` C++ class Solution { public: bool canFinish(int numCourses, vector>& prerequisites) { int arc_num = prerequisites.size(); stackstk; vectorin_degree(numCourses, 0); vector>G(numCourses, vector{}); for(auto &arc : prerequisites){ // 建图 in_degree[arc[0]]++; G[arc[1]].push_back(arc[0]); } for(int i = 0; i < numCourses; i++) // 先将所有入度为0的顶点入栈 if(!in_degree[i]) stk.push(i); int count = 0; while(!stk.empty()){ int course = stk.top(); stk.pop(); count++; for(int c: G[course]){ // 所有以course为起点的边的终点 if(!(--in_degree[c])) stk.push(c); } } return count == numCourses; } }; ``` ## DFS ``` C++ class Solution { private: bool DFS(vector>&G, vector& visited, int i) { if (visited[i] == -1) return false; if (visited[i] == 1) return true; visited[i] = -1; for (auto a : G[i]) { if (!DFS(G, visited, a)) return false; } visited[i] = 1; return true; } public: bool canFinish(int numCourses, vector>& prerequisites) { vector> G(numCourses, vector()); vector visited(numCourses, 0); for (auto &arc : prerequisites) { G[arc[1]].push_back(arc[0]); } for (int i = 0; i < numCourses; ++i) if (!DFS(G, visited, i)) return false; return true; } }; ```