Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的所有节点是否为一个强连通分量。
有两个概念:1.时间戳,2.追溯值
时间戳是dfs遍历节点的次序。
定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的栈中节点最小的次序号。由定义可以得出:
1 Low(u)=min{ 2 DFN(u), // 自己的次序号 3 Low(v), //(u,v)为树枝边,u为v的父节点 4 DFN(v), //(u,v)为指向栈中节点的后向边(非横叉边) 5 }
即以下节点的最小值:
1. 自己、子树节点的次序号
2. 指向栈中节点(后向边节点)的次序号[等价于 DFN(v)<DFN(u)且v不为u的父亲节点],这里不是横叉边(指向不在栈中的节点)。
当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。
伪码:
1 tarjan(u) 2 { 3 DFN[u]=Low[u]=++Index // 为节点u设定次序编号和Low初值 4 Stack.push(u) // 将节点u压入栈中 5 for each (u, v) in E // 枚举每一条邻边 6 if (v is not visted) // 如果节点v未被访问过 7 tarjan(v) // 继续向下找 8 Low[u] = min(Low[u], Low[v]) 9 else if (v in S) // 如果节点v还在栈内 10 Low[u] = min(Low[u], DFN[v]) 11 if (DFN[u] == Low[u]) // 如果节点u是强连通分量的根 12 repeat 13 v = S.pop // 将v退栈,为该强连通分量中一个顶点 14 print v 15 until (u== v) 16 }
运行Tarjan算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为O(N+M)。
一个顶点u是割点,当且仅当满足(1)或(2)
(1) u为树根,且u有多于一个子树。
(2) u不为树根,且满足存在(u,v)为树枝边(或称父子边,即u为v在搜索树中的父亲),使得DFN(u)<=Low(v)。即:若某点的子树们能回到的点大于等于自己,则该点为割点
一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFN(u)<Low(v)。