强连通分量(tarjan求强连通分量)

双DFS方法就是正dfs扫一遍,然后将边反向dfs扫一遍。《挑战程序设计》上有说明。

双dfs代码:

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <vector>
 5 
 6 using namespace std;
 7 const int MAXN = 1e4 + 5;
 8 vector <int> G[MAXN]; //图的邻接表
 9 vector <int> RG[MAXN]; //图的反向邻接表
10 vector <int> vs; //后序遍历的顶点顺序表
11 bool ok[MAXN]; //访问标记
12 int node[MAXN]; //所属强连通分量的序号
13 //第一次dfs
14 void dfs(int u) {
15     ok[u] = true;
16     for(int i = 0 ; i < G[u].size() ; i++) {
17         if(!ok[G[u][i]]) 
18             dfs(G[u][i]);
19     }
20     vs.push_back(u);
21 }
22 //第二次dfs
23 void rdfs(int u , int k) {
24     node[u] = k;
25     ok[u] = true;
26     for(int i = 0 ; i < RG[u].size() ; i++) {
27         if(!ok[RG[u][i]])
28             rdfs(RG[u][i] , k);
29     }
30 }
31 
32 int main()
33 {
34     int n , m , u , v;
35     while(~scanf("%d %d" , &n , &m) && (n || m)) {
36         for(int i = 1 ; i <= n ; i++) {
37             G[i].clear();
38             RG[i].clear();
39             ok[i] = false;
40             vs.clear();
41         }
42         for(int i = 0 ; i < m ; i++) {
43             scanf("%d %d" , &u , &v);
44             G[u].push_back(v);
45             RG[v].push_back(u);
46         }
47         //第一次dfs 选取任意的顶点作为起点遍历 后序遍历 越接近图尾部(叶子)的顶点顺序越小
48         for(int i = 1 ; i <= n ; i++) {
49             if(!ok[i]) 
50                 dfs(i);
51         }
52         memset(ok , false , sizeof(ok));
53         int k = 0;
54         //第二次dfs 将边反向遍历 以标记最大的顶点作为起点遍历 这样便可以给强连通分量标号
55         for(int i = vs.size() - 1 ; i >= 0 ; i--) {
56             if(!ok[vs[i]])
57                 rdfs(vs[i] , ++k);
58         }
59         if(k > 1) {
60             printf("No
");
61         }
62         else {
63             printf("Yes
");
64         }
65     }
66 }

tarjan代码虽然比双dfs多,理解也稍微复杂,但是用处比较多,像求LCA 桥 scc之类的问题。

推荐一个学scc的blog,讲的很好。 https://www.byvoid.com/blog/tag/%E5%9C%96%E8%AB%96

代码如下:

 1 //以hdu1269为例
 2 //强连通分量 tarjan算法
 3 #include <iostream>
 4 #include <cstdio>
 5 #include <cstring>
 6 #include <vector>
 7 using namespace std;
 8 const int MAXN = 1e4 + 5;
 9 vector <int> G[MAXN]; //临接表
10 bool instack[MAXN];  //i是否还在在栈里
11 int sccnum , index; //连通分量数 和时间顺序
12 int top , st[MAXN]; //存储i的模拟栈
13 int block[MAXN];  //i所属的连通分量
14 int low[MAXN] , dfn[MAXN];  //i能最早访问到的点 和时间戳
15 
16 void tarjan(int u) {
17     st[++top] = u;
18     instack[u] = true;
19     low[u] = dfn[u] = ++index;
20     for(int i = 0 ; i < G[u].size() ; i++) {
21         int v = G[u][i];
22         if(!dfn[v]) { //v点没访问过
23             tarjan(v);
24             if(low[v] < low[u]) { //回溯上来  low[v]表示的时间戳(连通分量的根)  u和v在一个连通分量里
25                 low[u] = low[v];
26             }
27         }
28         else if(instack[v]) {  //v还在栈里 说明u和v在一个连通分量(形成环路) v相当于连通分量的一个根
29             low[u] = min(low[u] , dfn[v]);
30         }
31     }
32     int v;
33     if(dfn[u] == low[u]) {  //是连通分量的根
34         sccnum++;
35         do {
36             v = st[top--];
37             block[v] = sccnum;
38             instack[v] = false;
39         }while(v != u); //连通分量的所有的点
40     }
41 }
42 
43 void init(int n) {
44     for(int i = 1 ; i <= n ; i++) {
45         G[i].clear();
46         instack[i] = false;
47         dfn[i] = 0;
48     }
49     sccnum = index = top = 0;
50 }
51 
52 int main()
53 {
54     int n , m , u , v;
55     while(~scanf("%d %d" , &n , &m) && (n || m)) {
56         init(n);
57         for(int i = 0 ; i < m ; i++) {
58             scanf("%d %d" , &u , &v);
59             G[u].push_back(v);
60         }
61         for(int i = 1 ; i <= n ; i++) {
62             if(!dfn[i]) {
63                 tarjan(i);
64             }
65         }
66         if(sccnum > 1) {
67             cout << "No
";
68         }
69         else {
70             cout << "Yes
";
71         }
72     }
73 }
原文地址:https://www.cnblogs.com/Recoder/p/5247992.html