强连通分量求解算法有Kosaraju和Tarjan_SCC,Kosaraju需要进行两遍dfs,而Tarjan只需一遍dfs,
可见Tarjan的运行效率比Kosaraju高,常数小。
而且以后求图的双联通分量、割、桥之类的还需要用到Tarjan,所以学习Tarjan的价值更大一些。
Tarjan算法的思想:
dfn[v]表示v节点在dfs中是第几个被搜索到得(时间戳),low[v]表示从v节点出发dfs过程中v下方节点(开始时间大于dfn[v],且由v可到达的节点) 所能到达的最早的节点的开始时间。初始时low[v]=dfn[v]=dfs_timer++
Tarjan算法基于dfs,当搜索一个节点v时:
将v加入栈中,将dfn[v]和low[v]全部置为dfs的搜索时间戳
搜索所有v能到达的节点u:
如果u已经被搜索过了 并且 并未求出它在其他强连通分量内,使low[v]=min(low[v],dfn[u]);★★★★★
如果u未被搜索过,则搜索u,并在搜索完u后使:low[v]=min(low[v],low[u]);
搜索完所有u后,如果dfn[v]==low[v]则表示 当前栈顶一直到v的节点在一个强连通分量中。
这个算法巧妙之处在此,如果节点v为强连通分量中的第一个被搜索到的节点,则必然有dfn[v]==low[v],因为★★★★★标记处的步骤。
蛮难理解的,看了好久。下面附一段tarjan:
void dfs(int v){ dfn[v]=low[v]=++dfs_time; st.push(v); for(int i=0;i<love[v].size();i++){ if(!dfn[love[v][i]]){ dfs(love[v][i]); low[v]=min(low[v],low[love[v][i]]); } else{ if(!scc[love[v][i]]) low[v]=min(low[v],dfn[love[v][i]]); //这句if的判断条件是:love[v][i]这个点是否在栈内 //即该点不在已求出的其他强连通分量内 } } if(dfn[v]==low[v]){ scc_cnt++; while(1){ int u=st.top();st.pop(); scc[u]=scc_cnt; if(u==v) break; } } } void Tarjan(){ for(int i=1;i<=n;i++){ if(scc[i]==0) dfs(i); } }
直接执行Tarjan()即可。
然后就去做了下codevs2822,写了好久最后被逼无奈用了并查集,心好累。
下面是标程:
1 #define MAXN 100010 2 #include<iostream> 3 #include<stack> 4 #include<algorithm> 5 #include<vector> 6 #include<cstdio> 7 #include<cstdlib> 8 using namespace std; 9 10 int n,m; 11 stack <int> st; 12 vector <int> love[MAXN]; 13 vector <int> scc_map[MAXN]; 14 int dfs_time,scc_cnt,lover_num; 15 int scc[MAXN],dfn[MAXN],low[MAXN],inner[MAXN]; 16 int a[MAXN],b[MAXN],ans[MAXN]; 17 int par[MAXN]; 18 19 void dfs(int v){ 20 dfn[v]=low[v]=++dfs_time; 21 st.push(v); 22 for(int i=0;i<love[v].size();i++){ 23 if(!dfn[love[v][i]]){ 24 dfs(love[v][i]); 25 low[v]=min(low[v],low[love[v][i]]); 26 } 27 else{ 28 if(!scc[love[v][i]]) low[v]=min(low[v],dfn[love[v][i]]); 29 //这句if的判断条件是:love[v][i]这个点是否在栈内 30 //即该点不在已求出的其他强连通分量内 31 } 32 } 33 34 if(dfn[v]==low[v]){ 35 scc_cnt++; 36 while(1){ 37 int u=st.top();st.pop(); 38 scc[u]=scc_cnt; 39 if(u==v) break; 40 } 41 } 42 } 43 44 void Tarjan(){ 45 for(int i=1;i<=n;i++){ 46 if(scc[i]==0) dfs(i); 47 } 48 } 49 50 int get_par(int a){ 51 if(par[a]!=a) par[a]=get_par(par[a]); 52 return par[a]; 53 } 54 void merge(int a,int b){ 55 if(get_par(a)==get_par(b)) return; 56 par[get_par(a)]=get_par(b); 57 } 58 //打到这要吐血了,还要用并查集 59 60 int get_lover(){ 61 for(int i=1;i<=scc_cnt;i++) par[i]=i; 62 for(int i=0;i<m;i++){ 63 if(scc[a[i]]!=scc[b[i]]){inner[scc[a[i]]]++;} 64 merge(scc[a[i]],scc[b[i]]); 65 //如果一个天使被所有人所爱,它必定不爱任何一个人,入度为0 66 } 67 for(int i=2;i<=scc_cnt;i++) if(get_par(i)!=get_par(1)) return -1; 68 for(int i=1;i<=scc_cnt;i++){ 69 if((!inner[i]) && scc_map[i].size()>1) return i; 70 } 71 return -1; 72 } 73 74 75 int main(){ 76 scanf("%d%d",&n,&m); 77 for(int i=0;i<m;i++){ 78 scanf("%d %d",&a[i],&b[i]); 79 love[a[i]].push_back(b[i]); 80 } 81 82 Tarjan(); 83 84 for(int i=1;i<=n;i++){ 85 if(scc_map[scc[i]].size()==1) lover_num++; 86 scc_map[scc[i]].push_back(i); 87 } 88 printf("%d\n",lover_num); 89 90 int j=0,boss=get_lover(); 91 if(boss==-1){cout<<-1<<endl;} 92 else{ 93 for(int i=0;i<scc_map[boss].size();i++) ans[j++]=scc_map[boss][i]; 94 sort(ans,ans+j); 95 for(int i=0;i<j-1;i++) printf("%d ",ans[i]); 96 printf("%d\n",ans[j-1]); 97 } 98 return 0; 99 }