强连通分量
简介
在阅读下列内容之前,请务必了解 图论相关概念 中的基础部分。
强连通的定义是:有向图 G 强连通是指,G 中任意两个结点连通。
强连通分量(Strongly Connected Components,SCC)的定义是:极大的强连通子图。
这里要介绍的是如何来求强连通分量。
Tarjan 算法
引入
Robert E. Tarjan(罗伯特·塔扬,1948~),生于美国加州波莫纳,计算机科学家。
Tarjan 发明了很多算法和数据结构。不少他发明的算法都以他的名字命名,以至于有时会让人混淆几种不同的算法。比如求各种连通分量的 Tarjan 算法,求 LCA(Lowest Common Ancestor,最近公共祖先)的 Tarjan 算法。并查集、Splay、Toptree 也是 Tarjan 发明的。
我们这里要介绍的是在有向图中求强连通分量的 Tarjan 算法。
DFS 生成树
在介绍该算法之前,先来了解 DFS 生成树,我们以下面的有向图为例:
有向图的 DFS 生成树主要有 4 种边(不一定全部出现):
- 树边(tree edge):示意图中以黑色边表示,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边。
- 反祖边(back edge):示意图中以红色边表示(即
),也被叫做回边,即指向祖先结点的边。 - 横叉边(cross edge):示意图中以蓝色边表示(即
),它主要是在搜索的时候遇到了一个已经访问过的结点,但是这个结点 并不是 当前结点的祖先。 - 前向边(forward edge):示意图中以绿色边表示(即
),它是在搜索的时候遇到子树中的结点的时候形成的。
我们考虑 DFS 生成树与强连通分量之间的关系。
如果结点
反证法:假设有个结点
Tarjan 算法求强连通分量
Tarjan 算法基于对图进行 深度优先搜索。我们视每个连通分量为搜索树中的一棵子树,在搜索过程中,维护一个栈,每次把搜索树中尚未处理的节点加入栈中。
在 Tarjan 算法中为每个结点
:深度优先搜索遍历时结点 被搜索的次序。 :在 的子树中能够回溯到的最早的已经在栈中的结点。设以 为根的子树为 。 定义为以下结点的 的最小值: 中的结点;从 通过一条不在搜索树上的边能到达的结点。
一个结点的子树内结点的 dfn 都大于该结点的 dfn。
从根开始的一条路径上的 dfn 严格递增,low 严格非降。
按照深度优先搜索算法搜索的次序对图中所有的结点进行搜索,维护每个结点的 dfn
与 low
变量,且让搜索到的结点入栈。每当找到一个强连通元素,就按照该元素包含结点数目让栈中元素出栈。在搜索过程中,对于结点
未被访问:继续对 进行深度搜索。在回溯过程中,用 更新 。因为存在从 到 的直接路径,所以 能够回溯到的已经在栈中的结点, 也一定能够回溯到。 被访问过,已经在栈中:根据 low 值的定义,用 更新 。 被访问过,已不在栈中:说明 已搜索完毕,其所在连通分量已被处理,所以不用对其做操作。
将上述算法写成伪代码:
实现
1 2 3 4 5 6 7 8 9 10 |
|
对于一个连通分量图,我们很容易想到,在该连通图中有且仅有一个
因此,在回溯的过程中,判定
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
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 37 |
|
时间复杂度
分量标号和拓扑序的关系
Tarjan 算法在处理过程中,实际上是按照某种 逆拓扑序 来发现强连通分量的,这是因为算法在深度优先搜索的过程中会先访问那些没有出边的节点,而这与拓扑排序的过程是相反的。
如果我们将图中的所有强连通分量缩成单个节点,那么在这些缩点后的节点形成的 DAG 中进行拓扑排序,得到的顺序将与 Tarjan 算法给出的强连通分量的标号顺序相反。
因此,可以说,在缩点后的 DAG 中,强连通分量(缩点后)的标号顺序是其拓扑序的逆序。但要注意的是,这种说法仅在考虑了强连通分量之间的依赖关系(即从一个强连通分量到另一个强连通分量的有向边)时才成立。单个强连通分量内部的节点由于存在环,所以内部并不满足拓扑序的定义。
Kosaraju 算法
引入
Kosaraju 算法最早在 1978 年由 S. Rao Kosaraju 在一篇未发表的论文上提出,但 Micha Sharir 最早发表了它。
过程
该算法依靠两次简单的 DFS 实现:
第一次 DFS,选取任意顶点作为起点,遍历所有未访问过的顶点,并在回溯之前给顶点编号,也就是后序遍历。
第二次 DFS,对于反向后的图,以标号最大的顶点作为起点开始 DFS。这样遍历到的顶点集合就是一个强连通分量。对于所有未访问过的结点,选取标号最大的,重复上述过程。
两次 DFS 结束后,强连通分量就找出来了,Kosaraju 算法的时间复杂度为
实现
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 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Garbow 算法
过程
Garbow 算法是 Tarjan 算法的另一种实现,Tarjan 算法是用 dfn 和 low 来计算强连通分量的根,Garbow 维护一个节点栈,并用第二个栈来确定何时从第一个栈中弹出属于同一个强连通分量的节点。从节点
当回溯到某一个节点
实现
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 |
|
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 |
|
应用
我们可以将一张图的每个强连通分量都缩成一个点。
然后这张图会变成一个 DAG,可以进行拓扑排序以及更多其他操作。
举个简单的例子,求一条路径,可以经过重复结点,要求经过的不同结点数量最多。
习题
本页面最近更新:2024/11/28 18:10:53,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:alphagocc, Anguei, Cat-shao, chenquanwen, cnszlijz, countercurrent-time, dongzhiwei-git, Douglas Zhou, Enter-tainer, Entiesci, F1shAndCat, Great-designer, H-J-Granger, HanwGeek, hghgthifg, hsfzLZH1, iamtwz, ImpleLee, Ir1d, JulieSigtuna, ksyx, Marcythm, mcendu, Menci, NachtgeistW, ouuan, Qiu-Quanzhi, revkiru, ShaoChenHeng, shawlleyw, shuzhouliu, Siger Young, sshwy, SukkaW, Tiphereth-A, Unnamed2964, Xeonacid
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用