1、相关概念
强连通图:在一个强连通图中,任意两个点都通过一定路径互相连通。
强连通分量:在一个非强连通图中极大的强连通子图就是该图的强连通分量。
2、伪代码
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是根则出栈,并求得一个强连通分量(其实满足等式之后,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
3、关于算法思想和代码的解释
算法输入的是一个有向图,定义了一个栈(用来存放已经访问过的待确定的强连通分量的点集合),每个节点包含两个属性,index和 lowlink,其中index是深度优先搜索出来的排序序号,lowlink是从这一个节点开始通过有向边能够访问到的节点中最小的那个序号,一般情况下v.index>=v.lowlink(至少这个点还是可以访问到自己的),那么什么时候v.index==v.lowlink呢,自然是他只能向下访问而回不去他的祖先的情况下,他所能访问到的点都不会比他自己的小,所以v.index==v.lowlink等价于他是另一个强连通分量的根!!忘了说明,每次访问到一个节点是需要需要入栈的,当访问到这个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访问到的点从未被访问过,那么需要递归计算这个点的属性,然后将w所能访问到的最小属性的点的序号和自己已经记录下来的能访问到的最小节点的序号比较,谁小取谁。
如果这个点已经访问过而且已经在栈中,那么表示是同一个强连通分量,比较w.index和v.lowlink取小的作为v.lowlink,如果w.index小,那么已经排除v是强连通分量的根的可能性,如果是v.lowlink小,由于是按照顺序排的,所以v.index<w.index,所以如果v.lowlink<w.index,那么v.lowlink<v.index,也可以排除他是根
那么如果w点已经访问过但是不在栈中,那么说明这是个已经弹出的一个强连通分量的点集元素,而且这个点集中不含有v(否则v不会现在出现),这种情况是不会出现的,因为如果后来的点可以访问到祖先,那么w怎么可能被当做是一个强连通分量的元素而已经出栈呢?
经过上面的过程结束对v.lowlink的求值,判断它是不是根,如果是的话,那么表示下一个强连通分量已经开始,这个点上面的所有点都应该被弹出作为一个强连通分量