题目描述:
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
提示:
1 <= numCourses <= 105
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
prerequisites[i] 中的所有课程对 互不相同
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/course-schedule
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
分析:
好久没写关于图的题了,一看到这道题,脑子里就想到了拓扑排序,但是如何拓扑排序已经忘的一干二净了。。。。而且图的存储结构也忘了,差点想写一个node结构体来存储图。。。
没办法,只能看题解了。题解使用邻接表来存储图的结构,我感觉这也算是比较直观易懂的一种存储图的方式。并且拓扑排序要求图里不能有环存在。
每个节点有三种状态,-1:未访问,0:正在访问,1:已访问。0:正在访问,代表以该节点为根,对能到达的相邻节点进行dfs,在尚未遍历完相邻节点时,该根节点标记为0:正在访问。因此,如果访问相邻节点时,它的状态已经是0:正在访问,说明图中有环(a对相邻节点b进行dfs,b对相邻节点a进行dfs),直接返回false;如果相邻节点状态为1:已访问,直接跳过(相邻节点已访问,说明以该相邻节点为根的子图已经全部被遍历完)。当访问完所有节点时,标记该根节点为1:已访问。
在dfs函数外层,通过for循环来对每个节点进行dfs,跳过其中已访问的节点。如果访问完整个图都没有环存在,则该图可以进行拓扑排序,返回true。
更详细的分析,请看题解:
https://leetcode-cn.com/problems/course-schedule/solution/ke-cheng-biao-by-leetcode-solution/
代码如下:
class Solution {
public:
// 邻接表来存储图的结构
vector<vector<int>> linjietable;
// visited用来记录某节点是否被访问,共有三个状态
// -1 未访问
// 0 正在访问
// 1 已访问
int visited[100005];
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
// memset用来初始化visited数组为-1,即所有节点都没有访问过
memset(visited,-1,sizeof(visited));
// 重新linjietable邻接表的大小
linjietable.resize(numCourses);
// 根据 prerequisites来构建邻接表
for(int i=0;i<prerequisites.size();i++)
linjietable[prerequisites[i][1]].push_back(prerequisites[i][0]);
// 遍历每个节点
for(int i=0;i<numCourses;i++)
{
// 如果该节点没有被访问过,就对该节点进行dfs
if(visited[numCourses]==1)
continue;
bool flag=dfs(prerequisites,i);
// 如果在dfs的过程中,发现图中有环,则返回false
if(flag==false)
return false;
}
return true;
}
// dfs的功能:以index为根节点,来对该节点所在的子图进行dfs,访问该子图中的所有节点
// 如果子图中有环,则返回false
bool dfs(vector<vector<int>>& prerequisites,int index)
{
// 得到以该节点为根的相邻节点的数量
int len=linjietable[index].size();
// 相邻节点数量为0,则直接返回true,因为已经遍历完了,且不存在环
if(len==0) return true;
// 当前节点设置为正在访问
visited[index]=0;
// 依次对相邻节点进行dfs
for(int i=0;i<len;i++)
{
// 如果该相邻节点已访问,则跳过(相邻节点已访问,说明以该相邻节点为根的子图已经全部被遍历完)
if(visited[linjietable[index][i]]==1)
continue;
// 如果该相邻节点正在访问,说明图中有环 直接返回false
if(visited[linjietable[index][i]]==0)
return false;
// 相邻节点没有被访问,则对相邻节点进行dfs
bool flag=dfs(prerequisites,linjietable[index][i]);
// 如果在对相邻节点进行dfs的过程中,发现了环,则直接返回false
if(flag==false)
return false;
}
// 相邻节点全部访问完毕,则该节点标记为已访问
visited[index]=1;
return true;
}
};