在有向图中判定环

  |  

摘要: 有向图的环

【对算法,数学,计算机感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:算法题刷刷
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings


在文章 在无向图中判定环 中,我们研究了无向图判定环的问题,本文以 207. 课程表 为模板题来看一下如何判断给定的有向图是否含有环,其算法为拓扑排序,如果拓扑排序不能将所有点排好,则图中存在环。用 BFS 或 DFS 进行拓扑排序均可。


有向图判定环

你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。

  • 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。

请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

提示:

1
2
3
4
5
1 <= numCourses <= 2000
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
prerequisites[i] 中的所有课程对 互不相同

示例 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:DFS

在使用 DFS 对无向图判定环时,当前考查当前顶点 $u$ 的边,如果某条边 $uv$,其顶点 $v$ 不是 prev 点而又在此前遍历过,则形成环。

但对于有向图,遇到以前遍历过的顶点时,并不代表形成环,办法是标记当前搜索路径,当回溯后,相应点要从标记中去掉。

1
2
3
visited[i] = 0 未考查过
visited[i] = 1 在当前搜索路径中
visited[i] = 2 已经考查过

DFS 判定有向图的环的逻辑中,只要在 DFS 回溯阶段加一个 list.add(v) 就可以顺便得到拓扑排序:拓扑排序

代码 (C++)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int> > g(numCourses);
vector<int> indegrees(numCourses, 0);
for(vector<int> &prerequisite: prerequisites)
{
g[prerequisite[1]].push_back(prerequisite[0]);
++indegrees[prerequisite[0]];
}
vector<int> visited(numCourses, 0);
for(int i = 0; i < numCourses; ++i)
{
if(visited[i] != 0) continue;
if(!dfs(g, i, visited))
return false;
}
return true;
}

private:
bool dfs(const vector<vector<int> >& g, int node, vector<int>& visited)
{
if(visited[node] == 2)
return true;
if(visited[node] == 1) // 环
return false;
visited[node] = 1;
for(int son: g[node])
if(!dfs(g, son, visited))
return false;
visited[node] = 2;
return true;
}
};

算法2:拓扑排序

如果不能用拓扑排序将图中所有点都排好,则图中存在环。

只有 DAG 才能进行拓扑排序。

代码 (C++)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int> > g(numCourses);
vector<int> indegrees(numCourses, 0);
for(vector<int> &prerequisite: prerequisites)
{
g[prerequisite[1]].push_back(prerequisite[0]);
++indegrees[prerequisite[0]];
}

queue<int> q;
for(int i = 0; i < numCourses; ++i)
{
if(indegrees[i] == 0)
{
q.push(i);
}
}

int n = numCourses;
while(!q.empty())
{
int cur = q.front();
q.pop();
--n;
for(int son: g[cur])
{
--indegrees[son];
if(indegrees[son] == 0)
q.push(son);
}
}
return n == 0;
}
};

Share