题目描述:
现在你总共有 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 完成。
算法1:BFS
判断拓扑排序中是否有环的存在
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int > >graph(numCourses, vector<int>(0) );//定义邻接表
vector<int > in(numCourses, 0);
for(auto a: prerequisites)
{
graph[a[1]].push_back(a[0]);
in[a[0]] ++;
}
queue<int> q;
for(int i=0; i<numCourses; i++)
{
if(in[i]==0)
q.push(i);
}
while(!q.empty())
{
int a = q.front();
q.pop();
for(auto num : graph[a])
{
in[num] --;
if(in[num]==0)
q.push(num);
}
}
for(int i=0; i<numCourses; i++) //检查是否有剩余 入度不为0的节点
if(in[i]!=0)
return false;
return true;
}
};
算法2:DFS
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int>>graph(numCourses, vector<int>(0));
vector<int>visit(numCourses, 0);
for(auto & num : prerequisites) // 构建邻接表,表示图结构
{
graph[num[1]].push_back(num[0]);
}
for(int i=0; i<numCourses; i++)
{
if(!dfs(graph, visit, i))
return false;
}
return true;
}
bool dfs(vector<vector<int> >& graph, vector<int>& visit, int index )
{
if(visit[index] == 1) // 已经遍历过的节点
return true;
if(visit[index] == -1) // 表示遍历的节点是正在遍历的节点,说明存在环了
return false;
visit[index] = -1; // 标记正在遍历的节点
for(auto a: graph[index])
{
if(!dfs(graph, visit, a))
return false;
}
visit[index] = 1; // 表示此节点不在环上
return true;
}
};