强连通分量(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\n"); 61 } 62 else { 63 printf("Yes\n"); 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\n"; 68 } 69 else { 70 cout << "Yes\n"; 71 } 72 } 73 }