Book--强连通分量
2014-10-14 23:01:26
最近学了一发强连通分量,来小结一下。
一、定义:在一个有向图中,如果任意两个点都是“相互可达”的,那么说这个图是强连通的。有向图的极大强连通子图,称为强连通分量(strongly connected components)。易得:强连通图的强连通分量就它自己。
二、具体求法:
(1)Tarjan
这个算法利用了DFS时间戳。思考:假设我们DFS搜到了一个SCC,那么从这个点(令为K点)搜下去,总能找到一个子节点(令为P点),使得P能返回K,那么从 K -> P 这一段DFS路径上的点就是处于一个SCC中的。我们发现这个过程是可以递归处理的,假设有两个SCC串在一次,那么DFS下去会先处理掉比较深的(也就是时间戳大的)SCC,然后回溯回去处理比较浅的SCC。所以要定理low[i]数组记录 i 点的子节点能返回的最祖先节点的时间戳,dfn[i]:i 点的时间戳。
1 void Dfs(int p){ 2 dfn[p] = low[p] = ++tot; 3 S.push(p); 4 for(int i = first[p]; i != -1; i = next[i]){ 5 int v = ver[i]; 6 if(!dfn[v]){//子节点未遍历过,则搜下去,回溯后更新当前点的low值 7 Dfs(v); 8 low[p] = min(low[p],low[v]); 9 } 10 else if(!sc[v]){//若子节点不属于其他scc且已经遍历过,那么这个点实际上 11 low[p] = min(low[p],dfn[v]);//是当前节点的祖先节点,直接用它的时间戳更新当前点low值 12 } 13 } 14 if(low[p] == dfn[p]){ 15 ++scnt; 16 while(1){ 17 int x = S.top(); 18 S.pop(); 19 sc[x] = scnt; 20 if(x == p) break; 21 } 22 } 23 } 24 25 void Tarjan(){ 26 memset(dfn,0,sizeof(dfn)); 27 memset(low,0,sizeof(low)); 28 memset(sc,0,sizeof(sc)); 29 while(!S.empty()) S.pop(); 30 for(int i = 1; i <= n; ++i) 31 if(!dfn[i]) Dfs(i); 32 }
(2)缩点
缩点的目的在于把有向图中的各个强连通分量缩成一个点,然后有向图就变成了DAG,更利于分析问题,而且方便记忆化搜索。
具体做法是:tarjan完后重新根据scc建图,扫一遍原图中的所有边,如果该边对应的两个点在同一个scc中,不用建边;否则,在两个scc间建一条有向边。转化为DAG。
(至于有人用并查集来做。。。不太了解)
(3)后言
watashi翻译的书里提供的是两次DFS的做法(需要将边反向),为kosaraju算法。总的来说tarjan算法的效率比kosaraju高30%

浙公网安备 33010602011771号