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%

posted @ 2014-10-14 23:31  Naturain  阅读(169)  评论(0)    收藏  举报