1.定义:
在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(SC---strongly connected)。
有向图中的极大强连通子图,成为强连通分量(SCC---strongly connected components)。
下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达,{5},{6}也分别是两个强连通分量。
tarjan算法的基础是DFS。我们准备两个数组Low和Dfn。Low数组是一个标记数组,记录该点所在的强连通子图所在搜索子树的根节点的 Dfn值,Dfn数组记录搜索到该点的时间,也就是第几个搜索这个点的。根据以下几条规则,经过搜索遍历该图(无需回溯)和 对栈的操作,我们就可以得到该有向图的强连通分量。
- 数组的初始化:当首次搜索到点p时,Dfn与Low数组的值都为到该点的时间。
- 堆栈:每搜索到一个点,将它压入栈顶。
- 当点p有与点p’相连时,如果此时(时间 = dfn[p]时)p’不在栈中,p的low值为两点的low值中较小的一个。
- 当点p有与点p’相连时,如果此时(时间 = dfn[p]时)p’在栈中,p的low值为p的low值和p’的dfn值中较小的一个。
- 每当搜索到一个点经过以上操作后(也就是子树已经全部遍历)的low值等于dfn值,则将它以及在它之上的元素弹出栈。这些出栈的元素组成一个强连通分量。
- 继续搜索(或许会更换搜索的起点,因为整个有向图可能分为两个不连通的部分),直到所有点被遍历。
///其时间复杂度也是O(N+M) #include <stdio.h> #include <string.h> #include <vector> #include <stack> #include <iostream> using namespace std; #define N 10005 /// 题目中可能的最大点数 stack<int>sta; /// 存储已遍历的结点 vector<int>gra[N]; /// 邻接表表示图 int dfn[N]; /// 深度优先搜索访问次序 int low[N]; /// 能追溯到的最早的次序 int InStack[N]; /// 检查是否在栈中(2:在栈中,1:已访问,且不在栈中,0:不在) vector<int> Component[N]; /// 获得强连通分量结果 int InComponent[N]; /// 记录每个点在第几号强连通分量里 int Index,ComponentNumber;/// 索引号,强连通分量个数 int n, m; /// 点数,边数 void init()///清空容器,数组 { memset(dfn, 0, sizeof(dfn)); memset(low, 0, sizeof(low)); memset(InStack, 0, sizeof(InStack)); Index = ComponentNumber = 0; for (int i = 1; i <= n; ++ i) { gra[i].clear(); Component[i].clear(); } while(!sta.empty()) sta.pop(); } void tarjan(int u) { InStack[u] = 2; low[u] = dfn[u] = ++ Index; sta.push(u);///寻找u所在的强连通分量 for (int i = 0; i < gra[u].size(); ++ i) { int t = gra[u][i]; if (dfn[t] == 0)///不在的继续递归 { tarjan(t);///递归到头了就 low[u] = min(low[u], low[t]); } else if (InStack[t] == 2)///在栈里 { low[u] = min(low[u], dfn[t]); } } if(low[u] == dfn[u])///sta出栈就是一个强连通分量的 { ++ComponentNumber;///强连通分量个数 while (!sta.empty()) { int j = sta.top(); sta.pop(); InStack[j] = 1;///已访问但不在栈中 Component[ComponentNumber].push_back(j); ///用vector存储第ComponentNumber个强连通分量 InComponent[j]=ComponentNumber; ///记录每个点在第几号强连通分量里 if (j == u) break; } } } void input(void) { for(int i=1; i<=m; i++) { int a,b; scanf("%d%d",&a,&b); gra[a].push_back(b);///有向图才有强连通分量 } } void solve(void) { for(int i=1; i<=n; i++) if(!dfn[i]) tarjan(i); if(ComponentNumber > 1) puts("No"); else puts("Yes"); } int main() { while(scanf("%d%d",&n,&m),n+m) { init(); input(); solve(); /*每一个强连通分量的具体数字 for(int i = 1; i <= ComponentNumber; i++) { for(int j = 0; j < Component[i].size(); j++) if(!j) cout << Component[i][j]; else cout <<"-->"<< Component[i][j]; cout<<endl; } */ } return 0; }
1 ///时间复杂度为O(n+m) 2 ///调用 3 ///1、init() 4 ///2、把图用add 存下来,注意图点标为1-n,若是[0,n-1]则给所有点++; 5 ///3、调用tarjan_init(n); 再调用suodian(); 6 ///4、新图就是vector<int>G[]; 新图点标从1-tar ; 7 ///5、对于原图中的每个点u,都属于新图中的一个新点Belong[u]; 8 ///新图一定是森林。 9 ///6、新图中的点u 所表示的环对应原图中的vector<int> bcc[u]; 10 ///7、旧图中u在新图中所属的点是Belong[u]; 11 #include <cstdio> 12 #include <cstring> 13 #include <vector> 14 #include <stack> 15 #include <iostream> 16 #include <algorithm> 17 #define repu(i, a, b) for(int i = a; i < b; i++) 18 using namespace std; 19 #define N 30100 20 ///N为最大点数 21 #define M 150100 22 ///M为最大边数 23 int n, m;///n m 为点数和边数 24 25 struct Edge 26 { 27 int from, to, nex; 28 bool sign;///是否为桥 29 } edge[M<<1]; 30 31 int head[N], edgenum; 32 33 void add(int u, int v) ///边的起点和终点 34 { 35 Edge E = {u, v, head[u], false}; 36 edge[edgenum] = E; 37 head[u] = edgenum++; 38 } 39 40 int DFN[N], Low[N], Stack[N], top, Time; 41 ///Low[u]是点集{u点及以u点为根的子树} 中(所有反向弧)能指向的(离根最近的祖先v) 的DFN[v]值(即v点时间戳) 42 int taj;///连通分支标号,从1开始 43 int Belong[N];///Belong[i] 表示i点属于的连通分支 44 bool Instack[N]; 45 vector<int> bcc[N]; ///标号从1开始 46 47 void tarjan(int u ,int fa) 48 { 49 DFN[u] = Low[u] = ++ Time ; 50 Stack[top ++ ] = u ; 51 Instack[u] = 1 ; 52 for (int i = head[u] ; ~i ; i = edge[i].nex ) 53 { 54 int v = edge[i].to ; 55 if(DFN[v] == -1) 56 { 57 tarjan(v , u) ; 58 Low[u] = min(Low[u] ,Low[v]) ; 59 if(DFN[u] < Low[v]) 60 edge[i].sign = 1;///为割桥 61 } 62 else if(Instack[v]) 63 Low[u] = min(Low[u] ,DFN[v]) ; 64 } 65 if(Low[u] == DFN[u])///找到一个强连通分量 66 { 67 int now; 68 taj ++ ; 69 bcc[taj].clear(); 70 do 71 { 72 now = Stack[-- top] ; 73 Instack[now] = 0 ; 74 Belong [now] = taj ; 75 bcc[taj].push_back(now);///存储一个强连通分量 76 } 77 while(now != u) ; 78 } 79 } 80 81 void tarjan_init(int all) 82 { 83 memset(DFN, -1, sizeof(DFN)); 84 memset(Instack, 0, sizeof(Instack)); 85 memset(Low, 0, sizeof(Low)); 86 top = Time = taj = 0; 87 for(int i=1; i<=all; i++) 88 if(DFN[i] == -1 ) 89 tarjan(i, i); ///注意开始点标!!! 90 } 91 92 vector<int>G[N]; 93 int du[N]; 94 void suodian() 95 { 96 memset(du, 0, sizeof(du)); 97 for(int i = 1; i <= taj; i++) 98 G[i].clear(); 99 for(int i = 0; i < edgenum; i++) 100 { 101 int u = Belong[edge[i].from], v = Belong[edge[i].to]; 102 if(u!=v) 103 G[u].push_back(v), du[v]++; 104 } 105 } 106 107 void init() 108 { 109 memset(head, -1, sizeof(head)); 110 edgenum=0; 111 } 112 113 int main() 114 { 115 while(scanf("%d%d",&n,&m) && (n + m)) 116 { 117 init(); 118 repu(i,0,m) 119 { 120 int a,b; 121 scanf("%d%d",&a,&b); 122 add(a,b); 123 } 124 tarjan_init(n); 125 // repu(i,1,taj + 1)///输出每一个强连通分量 126 // { 127 // for(int j = 0; j < bcc[i].size(); j++) 128 // cout<<bcc[i][j]<<" "; 129 // cout<<endl; 130 // } 131 if(taj > 1) 132 printf("No\n"); 133 else 134 printf("Yes\n"); 135 } 136 return 0; 137 }
参考链接http://blog.csdn.net/xinghongduo/article/details/6195337