交互式图论学习
交互式图论学习
Guest User
Using app without sign in
图中环查找器
在有向图和无向图中检测环
选择算法并生成步骤以开始可视化
环检测判定图中是否含有环,即返回其起始顶点的路径。技术在有向图与无向图之间有所不同:在有向图中环意味着循环依赖,在无向图中超出一棵树的任何多余边都会产生环。
在有向图中,DFS 对边进行分类:指向仍在递归栈上顶点的回边即证明存在环,用三种顶点状态(未访问、处理中、已完成)跟踪。在无向图中,DFS 遇到非父节点的已访问顶点时发现环,并查集则在某条边连接两个已在同一集合的顶点时检测到环。所有方法都运行在 O(V + E),并查集每条边近乎常数。
环检测防止操作系统中的死锁、捕捉构建工具和包管理器中的循环导入、验证电子表格和工作流定义,并把守拓扑排序的入口。Floyd 用于链表的龟兔算法变体是被问得最多的面试题之一。
有向图和无向图需要的检测方法确实不同。有向版本追踪递归栈,无向版本追踪父结点。
// 有向:三色 DFS
白 = 未访问,灰 = 在递归栈中,黑 = 已完成
有环(u):
颜色[u] = 灰
对 u 的每个邻居 v:
若 颜色[v] == 灰: 返回真 // 回边
若 颜色[v] == 白 且 有环(v):
返回真
颜色[u] = 黑
返回假
// 无向:携带父结点的 DFS
有环(u, 父):
已访问.加入(u)
对 u 的每个邻居 v:
若 v == 父: 跳过
若 v 在已访问中: 返回真
若 有环(v, u): 返回真
返回假这个区别比看上去更重要。在有向图中,抵达一个黑色结点是横叉边,完全不构成环,因此朴素的已访问检查会报告并不存在的环。在无向图中,跳过父结点正是防止把每一条边都读成双结点环的关键。
在一个既含环又含一条误导性横叉边的图上运行有向三色检测。
示例图: 有向边 A 到 B、A 到 C、B 到 D、C 到 D 以及 D 到 B。
该图确实含有一个环 B 到 D 到 B,由灰色检测找出。路径 A 到 C 到 D 并不是环,而区分这两种情形的唯一依据就是颜色。
时间: O(V + E) · 空间: O(V)
两种写法都是单次 DFS,每条边只做常数级的额外工作,因此代价与遍历本身相同。颜色数组或已访问集合为 O(V),再加 O(V) 的递归栈。针对无向图的并查集替代方案运行在 O(E alpha(V)),实际上是线性的;当边逐条到达、而你希望在边出现的那一刻就拒绝掉闭合环的边而不必重新遍历整个图时,它更为合适。
选择检测方法时,既要匹配边的方向,也要考虑图是静态的还是逐步构建的。
| 替代算法 | 以下情况更合适 | 代价 |
|---|---|---|
| 并查集 | 无向图,且边逐条到达。在加入时以近似常数时间拒绝闭合环的边。 | O(E·α(V)) |
| Kahn 拓扑排序 | 有向图,并且在无环时你还想要那个顺序。队列清空后剩下的结点恰好就是有环的部分。 | O(V + E) |
| Tarjan 强连通分量 | 有向图,且你想知道哪些结点位于环中,而不只是是否存在环。任何规模大于一的分量都是一个环。 | O(V + E) |
| Floyd 判圈算法 | 每个结点恰有一个后继的函数图或链表。只需 O(1) 内存。 | O(n) |
阅读完整文章: Graph Algorithms in Coding Interviews
相关算法: 深度优先搜索, 拓扑排序, 克鲁斯卡尔最小生成树算法