把一个图分解为各强连通分支(strongly-connected-component)是深度优先搜索的一个经典应用,下面就是如何使用深搜来将一个有向图分解为各强连通分支。
Kosaraju算法需要用到两次深搜,基本方法为:第一次深搜记录下各顶点搜索完成时的时间戳f[x](看到网上有人是用vector存放顺序是一个更好的改善),然后将原图G的边反向得到逆图G‘,接着对该逆图进行第二次深搜,跟第一次不同的是这一次搜索是根据之前得到的时间戳从大到小进行搜索(下面我将同一个强连通分支的顶点在同一行中输出,使用邻接矩阵表示图G和G’,但是邻接矩阵在做题时容易爆内存,可用连接表等代替之) C语言代码如下:
#include <stdio.h> #include <string.h> const int MAXN = 101; bool map[MAXN][MAXN]; //存放原图关系 bool map2[MAXN][MAXN]; //存放逆图关系 bool vis[MAXN]; //是否遍历过 int f[MAXN]; //遍历完成时间戳 int n, time; //节点数量,时间戳 void Firstly_DFS(int x){ //第一次深搜,计算出各个点完成时间戳f[x] int i; vis[x] = true; time++; for (i = 0; i < n; i++) { if (map[x][i] && !vis[i]) Firstly_DFS(i); } f[x] = ++time; // printf("%d %d\n", x, f[x]); } void Secondly_DFS(int x) { //第二次深搜,找出强连通分量 vis[x] = 1; printf("%d ", x); //将同一个强连通分量的节点在同一行输出 for (int i = 0; i < n; i++) { if (map2[x][i] && !vis[i]) Secondly_DFS(i); } } void Inverse() { //求逆图 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { map2[i][j] = map[i][j]; if (map[i][j] && !map[j][i]) map2[i][j] = 0; if (!map[i][j] && map[j][i]) map2[i][j] = 1; } } } void Strongly_Connected_Components() { memset(f, 0, sizeof(f)); memset(vis, 0, sizeof(vis)); time = 0; for (int i = 0; i < n; i++) { //对每一个未遍历节点进行深搜 if (!vis[i]) { Firstly_DFS(i); } } memset(vis, 0, sizeof(vis)); Inverse(); while (1) { int max = -1, v; //找出时间戳最大的点 for (int j = 0; j < n; j++) { if (!vis[j] && f[j] > max) { max = f[j]; v = j; } } //若找不到,说明所有强连通分量已经全部找出,否则进行第二次深搜 if (max == -1) break; Secondly_DFS(v); printf("\n"); } } int main() { int m, i, j, u, v; while (scanf("%d%d", &n, &m) != EOF) { memset(map, 0, sizeof(map)); for (i = 0; i < m; i++) { scanf("%d%d", &u, &v); map[u][v] = 1; } Strongly_Connected_Components(); } return 0; }
接下来是我对算法导论22.5强连通分支的理解:
首先需要理解下面几个引理(具体证明都不难,有用到白色路径定理):
1 .将各强连通分支缩成一个点后可形成一个DAG(有向无环图);
2. 若C和C‘分别是图G的两个不同的强连通分量,如果有一条边(u,v)属于E,其中u在C中,v在C’中,那么f(C)> f(C');
3.关键:若C和C‘分别是图G的两个不同的强连通分量,如果有一条边(u,v)∈ET(逆图),由引理2则课得出f(C)< f(C');
由引理3我们来分析第二次搜索过程,从f最大的强连通分支C开始,C不可能有指向其他强连通分支的边,因而得到的搜索树也就是该强连通分支了,并将该分支标记为遍历过,接下去其他搜索树也是相同的过程,没有边可以到达未遍历的分支,可到达的分支则是对应f(C)比较大的,该分支已经遍历过,因而也不会搜索到该分支上,所以通过归纳便可证明该算法的可行性。
以上是个人初学强连通分支的一点点感悟,有很多不对的地方希望大家能够指正,谢谢