现在你总共有 n 门课需要选,记为 0
到 n-1
。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?
示例 1:
输入: 2, [[1,0]] 输出: true 解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。
示例 2:
输入: 2, [[1,0],[0,1]] 输出: false 解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。
说明:
- 输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。
- 你可以假定输入的先决条件中没有重复的边。
提示:
- 这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。
- 通过 DFS 进行拓扑排序 - 一个关于Coursera的精彩视频教程(21分钟),介绍拓扑排序的基本概念。
-
拓扑排序也可以通过 BFS 完成。
这道题的思路是找图中是否存在环结构,如果存在环结构,就返回false,否则返回true,所以这道题简化成在找寻有向图是否存在环结构,我们使用邻接表来表示图结构,如果存在指向u到v的一条边,意味着v的课程需要先修u,使用vector<unordered_set<int>> graph来保存图结构。
思路一:BFS,步骤为:首先构建图结构,然后寻找每个节点的入度(indegree),找寻入度为0的节点,如果找不到直接返回false(因为存在圆环),然后把这个节点的邻接点的入度都减一,再寻找每个节点的入度是否为0,一直重复以上操作,如果没有返回false,最后返回true,证明图中没有圆环。
参考代码:
class Solution {
public:
vector<unordered_set<int>> make_graph(int numCourses, vector<pair<int, int>>& prerequisites) {
vector<unordered_set<int>> graph(numCourses);
for (auto item : prerequisites) {
graph[item.second].insert(item.first);
}
return graph;
}
vector<int> calculate_indegree(vector<unordered_set<int>> &graph) {
vector<int> indegree_node(graph.size());
for (auto neibors : graph) {
for (auto neibor : neibors) {
indegree_node[neibor]++;
}
}
return indegree_node;
}
bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
vector<unordered_set<int>> graph = make_graph(numCourses, prerequisites);
vector<int> indegree_node = calculate_indegree(graph);
for (int i = 0; i < numCourses; i++) {
int j = 0;
for (; j < numCourses; j++) {
if (indegree_node[j]==0) break;
}
if (j == numCourses) return false;
indegree_node[j] = -1;
for (auto neibor : graph[j]) {
indegree_node[neibor]--;
}
}
return true;
}
};
思路二:DFS,采用深搜的思路有点不一样,每次寻找一个节点,然后对其邻接点的每一个节点再次寻找其周围的邻接点,如果发现这个节点已经在之前找过了,证明有环,返回false,否则一直遍历结束。这里其实是对每一个节点(startVertex)都采用dfs,所以我们定义两个bool数组来保存:1 visited数组,迄今为止遍历到的所有节点的路径(即节点是否被访问过);2 onPath数组,保存每一次dfs的节点路径。
对于visited数组,只会被赋值true,一旦这个节点被赋值为true,意味着这个节点被访问过且经过这个节点的路径一定无环(因为我们是通过onPath数组来寻找是否有环的,和visited无关)
对于onPath数组,在每次dfs访问到节点时被赋值为true,退出栈时又被赋值为false(相当于对于下一次起始节点startVertex的dfs而言onPath全是false,onPath只对每一次dfs负责)
参考代码:
class Solution {
public:
vector<unordered_set<int>> make_graph(int numCourses, vector<pair<int, int>>& prerequisites) {
vector<unordered_set<int>> graph(numCourses);
for (auto item : prerequisites) {
graph[item.second].insert(item.first);
}
return graph;
}
bool has_cycle(vector<unordered_set<int>> &graph, vector<int> &visited, vector<int> &onPath,int startVertex) {
if (visited[startVertex]) return false;
visited[startVertex] = onPath[startVertex] = true;
for (auto neibor : graph[startVertex]) {
if (onPath[neibor] || has_cycle(graph,visited,onPath,neibor)) {
return true;
}
}
onPath[startVertex] = false;
return false;
}
bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
vector<unordered_set<int>> graph = make_graph(numCourses, prerequisites);
vector<int> visited(numCourses, false), onPath(numCourses, false);
for (int i = 0; i < numCourses; i++) {
if (!visited[i] && has_cycle(graph,visited,onPath,i)) {
return false;
}
}
return true;
}
};