Tarjan算法的操作原理如下:
- 在任何深度优先搜索中,同一强连通分量内的所有顶点均在同一棵深度优先搜索树中。也就是说,强连通分量一定是有向图的某个深度搜树子树。
- 我们用low值记录该点所在强连通子图对应的搜索子树的根节点的Dfn值。注意,该子树中的元素在栈中一定是相邻的,且根节点在栈中一定位于所有子树元素的最下方。
- 强连通分量是由若干个环组成的。所以,当有环形成时(也就是搜索的下一个点已在栈中),我们将这一条路径的low值统一,即这条路径上的点属于同一个强连通分量。
- 如果遍历完整个搜索树后某个点的dfn值等于low值,则它是该搜索子树的根。这时,它以上(包括它自己)一直到栈顶的所有元素组成一个强连通分量。
low值和Dfn值各代表什么:
Dfn值代表在一次深度优先搜索中各节点所处的次序,如果一个图是强连通的,它必然是可以被一个环串起所有的点,反之则不然,所以叶子节点必然是存在的。
什么样的节点可以看成根节点:low[v]=Dfn[v]。这个根节点是强连通子图的根节点。
== 伪代码 ==
'''algorithm''' tarjan '''is'''
'''input:''' 图 ''G'' = (''V'', ''E'')
'''output:''' 以所在的强连通分量划分的顶点集
''index'' := 0
''S'' := empty ''// 置栈为空''
'''for each''' ''v'' '''in''' ''V'' '''do'''
'''if''' (''v''.index is undefined)
strongconnect(''v'')
'''end if'''
'''function''' strongconnect(''v'')
''// 将未使用的最小index值作为结点v的index''
''v''.index := ''index''
''v''.lowlink := ''index''
''index'' := ''index'' + 1
''S''.push(''v'')
''// 考虑v的后继结点''
'''for each''' (''v'', ''w'') '''in''' ''E'' '''do'''
'''if''' (''w''.index is undefined) '''then'''
''// 后继结点w未访问,递归调用''
strongconnect(''w'')
''v''.lowlink := min(''v''.lowlink, ''w''.lowlink)
'''else if''' (''w'' is in ''S'') '''then'''
''// w已在栈S中,亦即在当前强连通分量中''
''v''.lowlink := min(''v''.lowlink, ''w''.index)
'''end if'''
''// 若v是根则出栈,并求得一个强连通分量''
'''if''' (''v''.lowlink = ''v''.index) '''then'''
start a new strongly connected component
'''repeat'''
''w'' := ''S''.pop()
add ''w'' to current strongly connected component
'''until''' (''w'' = ''v'')
output the current strongly connected component
'''end if'''
'''end function'''
变量<tt>index</tt>是深度优先搜索的结点计数器。<tt>S</tt>是栈,初始为空,用于存储已经访问但未被判定属于任一强连通分量的结点。注意这并非一个一般深度优先搜索的栈,结点不是在以它为根的子树搜索完成后出栈,而是在整个强连通分量被找到时。
最外层循环用于查找未访问的结点,以保证所有结点最终都会被访问。<tt>strongconnect</tt>进行一次深度优先搜索,并找到结点<tt>v</tt>的后继结点构成的子图中所有的强连通分量。
当一个结点完成递归时,若它的<tt>lowlink</tt>仍等于<tt>index</tt>,那么它就是强连通分量的根。算法将在此结点之后入栈(包含此结点)且仍在栈中的结点出栈,并作为一个强连通分量输出。