参考网址:
- 浅析强连通分量(Tarjan和kosaraju)
- Tarjan算法||配图很详细
- Kosaraju算法
- Kosaraju算法代码参考||关于如何输出逆后序的函数||关于证明其正确性的详细说明
- 全网最详细Tarjan算法
- 强连通法的个人理解||内有C++代码
强连通量性质:
reflexive:自反性
symmetric:对称性
transitive:传递性
Kosaraju算法
在计算科学中,Kosaraju的算法(又称为–Sharir Kosaraju算法)是一个线性时间(linear time)算法找到的有向图的强连通分量。它利用了一个事实,逆图(与各边方向相同的图形反转, transpose graph)有相同的强连通分量的原始图。
- Kosaraju也是基于深度优先搜索的算法。这个算法牵扯到两个概念,发现时间st,完成时间et。发现时间是指一个节点第一次被遍历到时的次序号,完成时间是指某一结点最后一次被遍历到的次序号。
- 在加边时把有向图正向建造完毕后再反向加边建一张逆图。
- 先对正图进行一遍dfs,遇到没访问过的点就让其发现时间等于目前的dfs次序号。在回溯时若发现某一结点的子树全部被遍历完,就让其完成时间等于目前dfs次序号。正图遍历完后将节点按完成时间入栈,保证栈顶是完成时间最大的节点,栈底是完成时间最小的节点。然后从栈顶开始向下每一个没有被反向遍历过的节点为起点对逆图进行一遍dfs,将访问到的点记录下来(或染色)并弹栈,每一遍反向dfs遍历到的点就构成一个强连通分量。
Kosaraju 算法过程伪代码:
首先,创建一个空表,存储已经遍历完的节点。
三种颜色代表含义:
- Gray : 正在遍历
- White:未遍历
- Black:遍历结束
assigned:相当于一个flag,标志是否遍历过,递归后每当检验到一个没有被标志的节点,强连通量SCC就加一。所以互相连通的节点时在同一个强连通分量里的。
SCC:计算连通分量。
先访问1(gray),到6(gray),到8(gray),再到7(black)(因为唯一的邻接点已经是gray),此时7遍历结束,放进L中。然后逆序回到8,回到6,回到1,将他们依次标为black。
寻找到第二个入度为0的点,此时选择3->5->2->4,没有下一个没遍历过的邻接点,所以依次将4->2->5>3标为black。
得到的连通分量:
- transpose:转置。
Kosaraju算法正确性证明:
引理:
总结一下就是只有两种情形:
①u的开始和结束都包含在v的开始和结束中,此时他们是连通的。
②u的开始和结束都包含在v的开始和结束外,此时他们没有关系。
Tarjan 算法
Tarjan 算法一种由Robert Tarjan提出的求解有向图强连通分量的算法,它能做到线性时间的复杂度。
定义:如果两个顶点可以相互通达,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。
在有向图G中,如果两点互相可达,则称这两个点强连通,如果G中任意两点互相可达,则称G是强连通图。
定理:
1、一个有向图是强连通的,当且仅当G中有一个回路,它至少包含每个节点一次。
2、非强连通有向图的极大强连通子图,称为强连通分量(SCC即Strongly Connected Componenet)
理解:
1.Tarjan算法是基于对图深度优先搜索DFS的算法,每个强连通分量为搜索树中的一棵子树。总的来说, Tarjan算法基于一个观察,即:同处于一个SCC中的结点必然构成DFS树的一棵子树。 我们要找SCC,就得找到它在DFS树上的根。
算法思想如下:
2. dfn[u]表示dfs时达到顶点u的次序号(时间戳),low[u]表示以u为根节点的dfs树中次序号最小的顶点的次序号,所以当dfn[u]=low[u]时,以u为根的搜索子树上所有节点是一个强连通分量。 先将顶点u入栈,dfn[u]=low[u]=++idx,扫描u能到达的顶点v,如果v没有被访问过,则dfs(v),low[u]=min(low[u],low[v]),如果v在栈里,low[u]=min(low[u],dfn[v]),扫描完v以后,如果dfn[u]=low[u],则将u及其以上顶点出栈。
时间戳(timestamp),通常是一个字符序列,唯一地标识某一刻的时间。
伪代码:
tarjan(u){
DFN[u]=Low[u]=++Index // 为节点u设定次序编号和Low初值
Stack.push(u) // 将节点u压入栈中
for each (u, v) in E // 枚举每一条边
if (v is not visted) // 如果节点v未被访问过
tarjan(v) // 继续向下找
Low[u] = min(Low[u], Low[v])
else if (v in S) // 如果节点u还在栈内
Low[u] = min(Low[u], DFN[v])
if (DFN[u] == Low[u]) // 如果节点u是强连通分量的根
repeat v = S.pop // 将v退栈,为该强连通分量中一个顶点
print v
until (u== v)
}
代码实现: