交互式图论学习
交互式图论学习
Guest User
Using app without sign in
强连通分量查找器
使用DFS和栈找到强连通分量
选择算法并生成步骤以开始可视化
Tarjan 算法在一次深度优先搜索中找出有向图的所有强连通分量(SCC)。强连通分量是极大的顶点集合,其中每个顶点都能通过有向路径到达其他任意顶点。
在一次 DFS 中,算法为每个节点赋予发现序号和 low-link 值,即从其子树出发、至多使用一条回边可达的最小序号。节点在访问时被压入栈。当某节点结束时其 low-link 等于自身序号,它就是一个 SCC 的根,栈被弹出到该节点以输出该分量。全部在一次遍历中以 O(V + E) 完成。
SCC 分解把有向图凝聚为有向无环图,是求解 2-SAT、分析编译器调用图、检测死锁以及在包管理器或电子表格中查找相互依赖环的第一步。Tarjan 的 low-link 值是经典的高难面试题。
一次 DFS、一个栈、每个结点两个数字。核心洞见是:每个强连通分量都有唯一的根,也就是该分量中最先被发现的那个结点。
strongconnect(u):
disc[u] = low[u] = ++时间
栈.压入(u); 在栈上[u] = 真
对每条边 (u, v):
若 v 未访问:
strongconnect(v)
low[u] = min(low[u], low[v])
否则若 在栈上[v]:
low[u] = min(low[u], disc[v])
// 否则:v 属于已完成的强连通分量,忽略
若 low[u] == disc[u]: // u 是分量的根
把栈弹到 u(含 u)为止
弹出的这一组就是一个强连通分量「在栈上」这项检查正是 Tarjan 与朴素 low-link 方案的分水岭。指向一个已访问、但已归入某个完成分量的结点的边,对你当前所在的分量没有任何信息,必须跳过;把它算进来会把两个本就彼此独立的强连通分量错误地合并。还要注意其中的不对称:树边并入的是 low[v],回边并入的是 disc[v],把两者搞混是另一个经典错误。
在一个同时包含三元环、二元环以及一个不属于任何环的结点的有向图上运行 Tarjan。
示例图: 有向边 A 到 B、B 到 C、C 到 A、B 到 D、D 到 E、E 到 D,以及 C 到 F。
最终的 low-link 值为 A 1、B 1、C 1、F 4、D 5、E 5,分量按 {F}、{D, E}、{A, B, C} 的顺序产出。有两点值得注意。分量是按缩点图的逆拓扑序输出的,这也是 Tarjan 通常作为求解 2-SAT 第一步的原因。另外,F 虽然能从环上抵达却无法返回,它被正确地划成独立分量,而没有被并入 {A, B, C}。
时间: O(V + E) · 空间: O(V)
一次深度优先搜索访问每个结点一次,并对每条有向边恰好检查一次,因此为 O(V + E)。每个结点只被压栈一次、弹栈一次,整个运行过程中的栈操作合计为 O(V)。额外状态是每个结点的发现时间、low-link 值和在栈标记,再加上递归栈,全都是 O(V)。Tarjan 一趟就能完成,而 Kosaraju 需要两次完整遍历外加构造转置图,这也是尽管两者都是线性的、实践中却通常首选 Tarjan 的原因。
三种线性的强连通分量算法渐进代价相同,因此选择取决于常数因子、内存以及代码写对的难易程度。
| 替代算法 | 以下情况更合适 | 代价 |
|---|---|---|
| Kosaraju 算法 | 你想要最容易讲解和实现的那个。两趟 DFS 外加一次转置。 | O(V + E),两趟 |
| 基于路径的强连通分量算法 | 你想要像 Tarjan 那样的一趟算法,但用两个栈代替 low-link 运算。 | O(V + E) |
| 并查集 | 图是无向的。连通分量比强连通分量简单得多。 | O(E·α(V)) |
| 缩点后做拓扑排序 | 你要的是分量构成的 DAG,而不只是分量本身。Tarjan 输出的顺序已经是逆拓扑序。 | O(V + E) |
阅读完整文章: Graph Algorithms and Their Complexity
相关算法: 科萨拉朱强连通分量算法, 深度优先搜索, 拓扑排序