强连通:
在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。
[vi-vj之间可以经过多个节点]
强连通图:
如果有向图G的每两个顶点都强连通,称G是一个强连通图。
强连通分量:
非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。
[即存在这样一个子图,图中的任意两个节点之间可以互相到达!]
在描述Tarjan算法之前,需要说明一些术语,否则可能很难弄明白算法所说的意思。
- 时间戳:深度优先搜索时,给每一个节点赋予一个叫做时间戳的属性。其中这个属性又分为两部分。节点v第一次被发现时给予一个开始时间戳(v.start_time),v的所有连通的节点都访问过后在给予节点v一个完成时间戳(v.finish_time)。实际上,v.start_time代表了DFS时的深度,v.finish_time表示的是DFS回溯到该节点时的所经过的点的个数。
- 树枝边:如果(u,v)是一条树枝边则表示u是v的父节点
- 后向边:如果(u,v)是一条后向边。说明v已经被访问,但其子孙后代还没有被访问完(正在访问中),且u是v的子孙后代,后向边又称返祖边.(因为回溯的时候回经过这条边找到祖先)
- 横叉边:如果(u,v)是一条横叉边。那么说明u、v这两个节点没有子孙关系,可能是兄弟,甚至可能不在一棵树上。
这样一些基本的概念就介绍完毕了,下面开始介绍Tarjan算法。
算法原理:
Tarjan算法的关键在于如何判定某结点是否是强连通分量的根。
注意“强连通分量的根”这一说法仅针对此算法,事实上强连通分量是没有特定的“根”的。在这里根结点指深度优先搜索时强连通分量中首个被访问的结点。
为找到根结点,我们给每个结点v一个深度优先搜索标号v.index,表示它是第几个被访问的结点(即DFS的深度)。此外,每个结点v还有一个值v.lowlink,表示从v出发经有向边可到达的所有结点中最小的index(即v和v的子树通过后向边所能到达的节点的最小DFS深度)。显然v.lowlink总是不大于v.index,且当从v出发经有向边不能到达其他结点时,这两个值相等。v.lowlink在深度优先搜索的过程中求得,v是强连通分量的根当且仅当v.lowlink = v.index。
这里我们有些疑问,按照道理来讲,v的后继节点的深度应该永远小于v的深度才对。但是不要忘记,很有可能出现“回边”(也就是后向边)的情况,这种情况下,v的后继节点的深度完全可以小于或者等于v的深度。
在算法描述用用dfn[u]存储u.index(DFS真实深度),用数组low[u]存储u.lowlink(u和u的子树通过后向边所能到达的节点的最小DFS深度)
下面给出伪代码:
algorithm tarjan is
input: 图 G = (V, E)
output: 以所在的强连通分量划分的顶点集
index := 0
S := empty // 置栈为空
for each v in V