有向图的环检测
https://labuladong.github.io/algo/2/19/35/https://mp.weixin.qq.com/s/xHmzLa4LtxOHEro0g3rBZw
Version 1: BFS+Adjacency List (Leetcode的207)
BFS的解法,用邻接表表示图(这里的邻接表用List<List<>>表示,而不是List<HashSet<>>,因为Testcase中二维数组有重复的所以用List而不用Set,对于重复的边,它在邻接表中村了两份,同时计算入度时也算了两次)一位数组indegree来表示每个顶点的入度。
我们开始先根据输入来建立这个有向图,同时将入度数组indegree也初始化好。
然后我们定义一个queue变量,将所有入度为0的点放入队列中,
然后开始遍历队列,从graph里遍历其连接的点,每到达一个新节点,将其入度减一,如果此时该点入度为0,则放入队列末尾。
直到遍历完队列中所有的值,若此时还有节点的入度不为0,则说明环存在,返回false,反之则返回true.
---------------------------------------------------------------------
其实 BFS 算法借助 indegree 数组记录每个节点的「入度」,也可以实现这两个算法。不熟悉 BFS 算法的读者可阅读前文 BFS 算法核心框架。
所谓「出度」和「入度」是「有向图」中的概念,很直观:如果一个节点x有a条边指向别的节点,同时被b条边所指,则称节点x的出度为a,入度为b。
先说环检测算法,直接看 BFS 的解法代码:
// 主函数
public boolean canFinish(int numCourses, int[][] prerequisites) {
// 建图,有向边代表「被依赖」关系
List<Integer>[] graph = buildGraph(numCourses, prerequisites);
// 构建入度数组
int[] indgree = new int[numCourses];
for (int[] edge : prerequisites) {
int from = edge[1], to = edge[0];
// 节点 to 的入度加一
indgree[to]++;
}
// 根据入度初始化队列中的节点
Queue<Integer> q = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (indgree[i] == 0) {
// 节点 i 没有入度,即没有依赖的节点
// 可以作为拓扑排序的起点,加入队列
q.offer(i);
}
}
// 记录遍历的节点个数
int count = 0;
// 开始执行 BFS 循环
while (!q.isEmpty()) {
// 弹出节点 cur,并将它指向的节点的入度减一
int cur = q.poll();
count++;
for (int next : graph[cur]) {
indgree[next]--;
if (indgree[next] == 0) {
// 如果入度变为 0,说明 next 依赖的节点都已被遍历
q.offer(next);
}
}
}
// 如果所有节点都被遍历过,说明不成环
return count == numCourses;
}
// 建图函数
List<Integer>[] buildGraph(int n, int[][] edges) {
// 见前文
}我先总结下这段 BFS 算法的思路:
1、构建邻接表,和之前一样,边的方向表示「被依赖」关系。
2、构建一个 indegree 数组记录每个节点的入度,即 indegree[i] 记录节点 i 的入度。
3、对 BFS 队列进行初始化,将入度为 0 的节点首先装入队列。
4、开始执行 BFS 循环,不断弹出队列中的节点,减少相邻节点的入度,并将入度变为 0 的节点加入队列。
5、如果最终所有节点都被遍历过(count 等于节点数),则说明不存在环,反之则说明存在环。
我画个图你就容易理解了,比如下面这幅图,节点中的数字代表该节点的入度:
队列进行初始化后,入度为 0 的节点首先被加入队列:
开始执行 BFS 循环,从队列中弹出一个节点,减少相邻节点的入度,同时将新产生的入度为 0 的节点加入队列:
继续从队列弹出节点,并减少相邻节点的入度,这一次没有新产生的入度为 0 的节点:
继续从队列弹出节点,并减少相邻节点的入度,同时将新产生的入度为 0 的节点加入队列:
继续弹出节点,直到队列为空:
这时候,所有节点都被遍历过一遍,也就说明图中不存在环。
反过来说,如果按照上述逻辑执行 BFS 算法,存在节点没有被遍历,则说明成环。
比如下面这种情况,队列中最初只有一个入度为 0 的节点:
当弹出这个节点并减小相邻节点的入度之后队列为空,但并没有产生新的入度为 0 的节点加入队列,所以 BFS 算法终止:
你看到了,如果存在节点没有被遍历,那么说明图中存在环,现在回头去看 BFS 的代码,你应该就很容易理解其中的逻辑了。
Version 2: DFS+Adjacency List (Leetcode的207)
用visited 和onPath数组来做图的DFS。
不过如果出题人继续恶心你,让你不仅要判断是否存在环,还要返回这个环具体有哪些节点,怎么办?
你可能说,onPath 里面为 true 的索引,不就是组成环的节点编号吗?
不是的,假设下图中绿色的节点是递归的路径,它们在 onPath 中的值都是 true,但显然成环的节点只是其中的一部分:
Last updated
Was this helpful?