前言
“Tarjan 陪伴强连通分量
生成树完成后思路才闪光
欧拉跑过的七桥古塘
让你 心驰神往”
——《膜你抄》
引入
什么是强联通分量(SCC)呢?
有向图强连通分量:在有向图 G G G 中,如果两个顶点 v i , v j v_i,v_j vi,vj 间( v i > v j v_i>v_j vi>vj)有一条从 v i v_i vi 到 v j v_j vj 的有向路径,同时还有一条从 v j v_j vj 到 v i v_i vi 的有向路径,则称两个顶点强连通(strongly connected)。如果有向图 G G G 的每两个顶点都强连通,称 G G G 是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。
看下面这个有向图:
它有 3 3 3 个强联通分量,如图:
显然,任何一个强连通分量之中的所有点之间均可到达。
那我们怎么求出一个图中的强连通分量呢?
直接DFS?显然会超时。
对于这个问题,美国计算机科学家Robert Tarjan(罗伯特·塔扬)提出了著名的Tarjan算法。
【题外话】
CSP之前膜拜大佬增加RP:
Tarjan算法
首先,我们应该了解一下DFS生成树,即按照DFS次序形成的树。
以下选自OI Wiki
有向图的 DFS 生成树主要有 4 种边(不一定全部出现):
- 树边(tree edge):示意图中以黑色边表示,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边。
- 反祖边(back edge):示意图中以红色边表示(即 7 → 1 7\rightarrow1 7→1),也被叫做回边,即指向祖先结点的边。
- 横叉边(cross edge):示意图中以蓝色边表示(即 9 → 7 9\rightarrow7 9→7),它主要是在搜索的时候遇到了一个已经访问过的结点,但是这个结点 并不是 当前结点的祖先时形成的。
- 前向边(forward edge):示意图中以绿色边表示(即 3 → 6 3\rightarrow6 3→6),它是在搜索的时候遇到子树中的结点的时候形成的。
以上选自OI Wiki
对于文章开头的那张图,我们尝试对它进行DFS,组成DFS树:
我们发现,那条反祖边(红边)指向了DFS树的根,形成了一个环,环是强连通的。
但是,我们怎么知道这个环是不是强联通分量呢?
很显然,我们只需要找到一条终点是DFS树的根,起点尽量靠下的反祖边,这条反祖边形成的一定是SCC。
注意,这里说的DFS树不一定是整个图的DFS树,也可以是子图的DFS树。
然而,我们又怎么确定哪些点是在SCC中呢?
如果结点 u u u 是某个强连通分量在搜索树中遇到的第一个结点,那么这个强连通分量的其余结点肯定是在搜索树中以 u u u 为根的子树中。 ——OI Wiki
为什么呢?
即得易见平凡,仿照上例显然,留作习题答案略,读者自证不难。反之亦然同理,推论自然成立,略去过程QED ,由上可知证毕。
——《西江月·证明》
我们假设一个点 x x x 在SCC中却不在以 u u u 为根的子树中,那么从 u u u 到 x x x 的过程中一定有一个反祖边或横叉边使其在子树外,而根据定义,这两种边都在 u u u 之前被搜索到,与“结点 u u u 是某个强连通分量在搜索树中遇到的第一个结点”矛盾,得证。
我们只需要开一个栈,从树根开始,把遍历到的点都加到栈里。当我们发现一个SCC存在时,就不断地弹出栈,直到弹出DFS树的树根为止。这样,弹出的节点就是SCC中的节点。
那么,算法的基本原理已经了解了,我们来具体地实现一下。
首先,我们要记录每个节点是第几个被搜索到的(即时间戳),记作