你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]
给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?
示例2:
输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。
方法一:BFS
分析:
这道题即判断一个有向图中是否有环,可以用拓扑排序算法来解决。过程如下:
1.从有向图中选择没有前驱(入度为0)的顶点输出。
2.依次删除1中的顶点,并删除这些顶点发出的全部边。
3.重复上述2步,直到图中不存在没有前驱的顶点为止。若此时还有顶点剩余则说明图中有环。
这道题还需要考虑的一个关键问题是如何表示图。
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
//建立图,graph[i]包含所有结点i指向的结点,有点类似图的邻接表存储结构
vector<vector<int>> graph(numCourses,vector<int>());
//记录每个结点的入度
vector<int> in(numCourses,0);
for(auto i:prerequisites){
graph[i[1]].push_back(i[0]);
in[i[0]]++;
}
queue<int> q;
for(int i=0;i<numCourses;++i){
if(in[i]==0)q.push(i);
}
while(!q.empty()){
int front=q.front();
q.pop();
for(auto i:graph[front]){
in[i]--;
if(in[i]==0) q.push(i);
}
}
//如果还有结点的入度不为0,则说明图中存在环
for(int i=0;i<numCourses;++i)
if(in[i]!=0) return false;
return true;
}
方法二:DFS
分析:
需要使用一个访问数组来记录当前节点的访问情况。访问数组的取值:
0:未被访问
1:已经被访问,即完成回溯
-1:正在被访问,还未回溯完成
具体过程:
深度优先遍历访问每个节点。在使用深度优先遍历访问数组的时候先将当前节点的访问状态置为-1,表示正在访问该节点。然后递归的去访问该节点所有指向的节点。最后将该节点的访问状态置为1,表示已经访问过了。在递归访问节点的过程中只要发现当前待访问节点的访问状态为-1,则表示出现了环;若访问状态为1,则无需在访问该节点。
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int>> graph(numCourses, vector<int>());
vector<int> visit(numCourses);
for (auto a : prerequisites) {
graph[a[1]].push_back(a[0]);
}
//注意这里是有向图,当且仅当所有结点都能回溯到自身位置才能说明图中不存在环
for (int i = 0; i < numCourses; ++i) {
if (!canFinishDFS(graph, visit, i)) return false;
}
return true;
}
//返回值表示从结点i开始是否能完成深度优先搜索并回溯到原点
bool canFinishDFS(vector<vector<int>>& graph, vector<int>& visit, int i) {
//当某个结点还未完成回溯时又被访问,说明图中存在环
if (visit[i] == -1) return false;
//因为canFinish函数中对所有结点都调用了该函数,因此在此之前可能该结点被访问过且能够回溯到原点,加入这一行可减少运行时间
if (visit[i] == 1) return true;
//正在被访问
visit[i] = -1;
for (auto a : graph[i]) {
if (!canFinishDFS(graph, visit, a)) return false;
}
//回溯到原点表示访问完成
visit[i] = 1;
return true;
}