求强连通分量的著名算法:Kosaraju算法,Gabow算法和Tarjan算法。其中Kosaraju算法要对原图和逆图都进行一次DFS,而另外两种算法只要进行一次DFS即可。[i]文是介绍Gabow算法的论文。
17.1 Kosaraju算法
Kosaraju算法虽然要进行两次DFS,但是复杂度仍然是O(V+E),而且比较容易理解。
17.1.1实例
PKU JudgeOnline, 2186, Popular Cows.
17.1.2问题描述
有一群牛,总数为N。给出牛之间的M个仰慕关系,该关系可以传递,比如:1仰慕2,2仰慕3,那么1也仰慕3,如果一头牛被所有的牛都仰慕,那么它将是最受欢迎的牛,求出有多少牛是最受欢迎的。
先输入N、M,再输入M个仰慕关系。
17.1.3输入
33
12
21
2 3
17.1.4输出
1
17.1.5分析
先对图求强连通分支,将所有强连通子图合并为一个结点,形成一个新图。
不难证明:在图中,如果将强连通分支看做一个结点,那么如果该结点出度不为0,则该SCC中的牛不是被所有牛仰慕的牛。反证法即可证明。
更进一步,不难证明:如果图中有牛被所有牛仰慕,那么有且只有一个出度为0的,且从图中的任何一个结点都能到达(连通性)的SCC,该SCC包含被所有牛仰慕的牛。反证法可以证明。
也可以知道:如果超过一个SCC的出度为0,那么连通性得不到保障。
可以证明:存在所有牛仰慕的牛,当且仅当出度为0的SCC有且只有一个。“仅当”很容易证明,下面证明“当”。假设只有一个SCC出度不为0时,但是却不存在所有牛仰慕的牛。那么必然意味着,有结点不能到达该SCC。因为如果所有结点都能到达该SCC,那么很自然的该SCC就是包含所有被其它所有牛仰慕的牛。在不能到达该SCC的结点中至少存在一个结点,其出度为0。因为如果所有结点的出度都不为0,那么所有结点必然形成一个环。形成环的结点在构造强连通分支的时候是要合并在一起的,矛盾。所以必然至少存在一个结点,其出度为0。假设和结果矛盾,故此,命题得证。
所以要求被所有牛仰慕的牛的个数,只需要求强连通分支,然后统计出度为0的个数。如果个数不为1则输出0。然后找到出度为0的那个SCC,。如果能就输出该SCC的牛的个数。
1.1.6程序
#include <stdio.h> #include <string.h> #define G_size 100000 //边的最大个数 #define V_size 11000 //点的最大个数 typedef struct Graph { int id;//记录了结点的序号 int next; }Graph; typedef struct Edge { int s, e; }Edge; EdgeE[G_size]; GraphGA[G_size]; GraphGT[G_size]; int N, M; int G_end; int order[V_size]; int id[V_size];//在逆序的时候记录了SCC的序号 int vis[V_size];//遍历图的时候使用 int in[V_size];//计算反向SCC图的入度,也就是逆向之前的SCC图出度 int cnt, scnt, pos; void Insert(int s, int e) //建立原图和逆图 { int p; p = s; while(GA[p].next){ p = GA[p].next; } GA[G_end].id = e; GA[p].next = G_end; p = e; while(GT[p].next){ p= GT[p].next; } GT[G_end].id = s; GT[p].next = G_end; G_end++; } void DFST(int x) //对原图进行搜索 { int p, q; vis[x] = 1; p = GT[x].next; while(p){ q = GT[p].id; if(!vis[q]) { DFST(q); } p = GT[p].next; } order[cnt++] = x; } void DFSA(int x) //对逆图进行搜索 { int p, q; vis[x] = 1; id[x] = cnt; p = GA[x].next; while (p){ q = GA[p].id; if(!vis[q]) { DFSA(q); } p = GA[p].next; } } void Solve() //主要过程 { int s, e; int i; memset(GA, 0, sizeof(GA)); memset(GT, 0, sizeof(GT)); memset(E, 0, sizeof(E)); G_end = N + 1; for (i = 0;i < M; i++) { scanf("%d%d", &s, &e); E[i].s = s - 1; E[i].e = e - 1; Insert(s - 1, e - 1); } memset(vis, 0, sizeof(vis)); cnt = 0; for (i = 0;i < N; i++) { if(!vis[i]) { DFST(i); } } memset(vis, 0, sizeof(vis)); cnt = 0; for (i = N- 1; i >= 0; i--) { if(!vis[order[i]]) { DFSA(order[i]); cnt++; } } for (i = 0;i < M; i++) { s = id[E[i].s]; e = id[E[i].e]; if (s!= e) { in[s]++; } } scnt = cnt; cnt = 0; for (i = 0;i < scnt; i++){ if(in[i] == 0) { pos = i; cnt++; } } if (cnt !=1){ printf("0\n"); } else{ cnt = 0; for (i= 0; i < N; i++){ if(in[id[i]] == pos) { cnt++; } } printf("%d\n",cnt); } } int main() { while (EOF!= scanf("%d %d", &N, &M)) Solve(); return 0; }17.2 Kosaraju算法判断单向连通性
17.2.1实例
PKU JudgeOnline, 2762, Going from u to v or from v to u?.
17.2.2问题描述
给定N个点和这N个点之间的M个有向连接。如果两个点之间能从其中一个点到另一个点,那么这两个点就是单向连通的。问这N个点是不是都是单向连通的。
先输入测试个数。每个测试,先输入N、M,然后是M个连接。
17.2.3输入
1
33
12
23
31
17.2.4输出
Yes
17.2.5分析
可以通过求强连通分支,简化图形,使得每两个结点只有单向连接。
然后使用DFS方法,对新图进行拓扑排序。
不难证明:如果原图是单向连通的,那么拓扑排序之后的结点必有指向下一个结点的连接。这是因为:假设没有这个连接,原图又是连通的,会拓扑排序的定义相违背。拓扑排序:对于有向无回路图,进行排序之和,如果包含边(u, v)那么u就出现在v之前。
同时不难证明:如果原图是单向连通的,那么新图有且只有一个结点的入度为0。首先,强连通分支将所有的回路聚合了,所以新图不存在回路,故此至少有一个结点的入度为0。其次,如果超过一个结点的入度为0,那么这两个结点肯定不能到达彼此。
故此,只需要先求强连通分支,建立新图,然后新图判断入度为0的结点个数是不是只有一个。然后以这个结点为根,DFS遍历新图,进行拓扑排序。最后,判断排序好的结点到下一个结点是不是有连接。
这里用到的强连通算法仍然是Kosaraju算法,但是由于超时,就对原来的程序进行了优化。主要的优化在于加入数组,记录每个结点的最后一个子结点的保存位置。典型的空间换时间。
1.2.6程序
#include <stdio.h> #include <string.h> #include <iostream> using namespace std; #define G_size 10100 //边的最大个数 #define V_size 1010 //点的最大个数 typedef struct Graph { int id;//记录了结点的序号 int next; }Graph; typedef struct Edge { int s, e; }Edge; EdgeE[G_size]; GraphGA[G_size]; GraphGT[G_size]; int N, M; int G_end; int order[V_size]; int id[V_size];//在逆序的时候记录了边所属的SCC的序号 int vis[V_size];//遍历图的时候使用 int in[V_size];//计算SCC图的入度 int cnt, scnt, pos; int lastSonGA[V_size]; int lastSonGT[V_size]; void Insert(int s, int e) //建立原图和逆图 { int p; p = lastSonGA[s]; GA[G_end].id = e; GA[p].next = G_end; lastSonGA[s] = G_end; p = lastSonGT[e]; GT[G_end].id = s; GT[p].next = G_end; lastSonGT[e] = G_end; G_end++; } void DFST(int x) //对逆图进行搜索 { int p, q; vis[x] = 1; p = GT[x].next; while(p){ q = GT[p].id; if(!vis[q]) { DFST(q); } p = GT[p].next; } order[cnt++] = x; } void DFSA(int x) //对原图进行搜索 { int p, q; vis[x] = 1; id[x] = cnt; p = GA[x].next; while (p){ q = GA[p].id; if(!vis[q]) { DFSA(q); } p = GA[p].next; } } int GSCC_end; GraphGSCC[G_size]; int lastSonGSCC[V_size]; void InsertGSS(int s, int e) //建立原图和逆图 { int p; p = lastSonGSCC[s]; GSCC[GSCC_end].id = e; GSCC[p].next = GSCC_end; lastSonGSCC[s] = GSCC_end; GSCC_end++; } int TopologcalOrder[V_size]; void DFS_GSS(int x) { int p, q; vis[x] = 1; p = GSCC[x].next; while (p){ q = GSCC[p].id; if(!vis[q]) { DFS_GSS(q); } p = GSCC[p].next; } TopologcalOrder[cnt++] = x; } int linked[V_size][V_size]; void Solve() //主要过程 { int s, e; int p; int i; int fail; memset(GA, 0, sizeof(GA)); memset(GT, 0, sizeof(GT)); memset(E, 0, sizeof(E)); for(i = 1;i <= N; i++){ lastSonGT[i] = i; lastSonGA[i] = i; } G_end = N + 1; for (i = 1;i <= M; i++) { scanf("%d%d", &s, &e); E[i].s = s; E[i].e = e; Insert(s, e); } memset(vis, 0, sizeof(vis)); cnt = 0; for (i = 1;i <= N; i++) { if(!vis[i]) { DFST(i); } } memset(vis, 0, sizeof(vis)); cnt = 0; for (i = N- 1; i >= 0; i--) { if(!vis[order[i]]) { cnt++; DFSA(order[i]); } } scnt = cnt; for(i = 1;i <= cnt; i++){ lastSonGSCC[i] = i; } GSCC_end = cnt + 1; memset(GSCC, 0, sizeof(GSCC)); memset(in, 0, sizeof(in)); for (i = 1;i <= M; i++) { s = id[E[i].s]; e = id[E[i].e]; if (s!= e) { InsertGSS(s, e); linked[s][e] = 1; in[e]++; } } cnt = 0; for (i = 1;i <= scnt; i++){ if(in[i] == 0) { pos = i; cnt++; } } fail = 0; if (cnt !=1){ fail = 1; }else{ memset(vis, 0, sizeof(vis)); cnt = 0; DFS_GSS(pos); for(i =scnt - 1; i > 0; i--) { s = TopologcalOrder[i]; e = TopologcalOrder[i - 1]; if(linked[s][e]== 0)// && linked[e][s] == 0) { fail = 1; break; } } } if(fail ==1) { printf("No\n"); }else{ printf("Yes\n"); } } int main() { int cases; scanf("%d",&cases); for(; cases> 0; cases--){ scanf("%d%d", &N, &M); Solve(); } return 0; }17.3 实例
PKU JudgeOnline, 2186, Popular Cows.
PKU JudgeOnline, 2762, Going from u to v or from v to u?.
[i] Path-Based Depth-first Search for Strong and Biconnected Components.Harold N. Gabow. Information Processing Letters 74 (2000) 107-114.