tarjan算法,一個關於 圖的聯通性的神奇算法

一.算法簡介node

Tarjan 算法一種由Robert Tarjan提出的求解有向圖強連通份量的算法,它能作到線性時間的複雜度。算法

 

咱們定義:ide

若是兩個頂點能夠相互通達,則稱兩個頂點強連通(strongly connected)。若是有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。有向圖的極大強連通子圖,稱爲強連通份量(strongly connected components)。學習

例如:在上圖中,{1 , 2 , 3 , 4 } , { 5 } ,  { 6 } 三個區域能夠相互連通,稱爲這個圖的強連通份量。spa

Tarjan算法是基於對圖深度優先搜索的算法,每一個強連通份量爲搜索樹中的一棵子樹。搜索時,把當前搜索樹中未處理的節點加入一個堆棧,回溯時能夠判斷棧頂到棧中的節點是否爲一個強連通份量。.net

再Tarjan算法中,有以下定義。3d

DFN[ i ] : 在DFS中該節點被搜索的次序(時間戳)code

LOW[ i ] : 爲i或i的子樹可以追溯到的最先的棧中節點的次序號component

當DFN[ i ]==LOW[ i ]時,爲i或i的子樹能夠構成一個強連通份量。blog

 

二.算法圖示

以1爲Tarjan 算法的起始點,如圖

順次DFS搜到節點6

 回溯時發現LOW[ 5 ]==DFN[ 5 ] ,  LOW[ 6 ]==DFN[ 6 ] ,則{ 5 } , { 6 } 爲兩個強連通份量。回溯至3節點,拓展節點4.

拓展節點1 , 發現1再棧中更新LOW[ 4 ],LOW[ 3 ] 的值爲1

 回溯節點1,拓展節點2

自此,Tarjan Algorithm 結束,{1 , 2 , 3 , 4 } , { 5 } ,  { 6 } 爲圖中的三個強連通份量。

不難發現,Tarjan Algorithm 的時間複雜度爲O(E+V).

 

學習處  

模板:

 1 void dfs(int u)  2 {  3     times++;//記錄dfn順序 
 4     dfn[u]=times;//賦值 
 5     low[u]=times;//先賦初值 
 6     vis[u]=true;//vis[i]用來判斷i是否搜索過;
 7     insta[u]=true;//表示是否在棧中,true爲在棧中; 
 8     stack[top]=u;//棧頂 
 9     top++; 10     for(int i=head[u];i!=-1;i=edge[i].next)// 以建圖順序枚舉此點所連的邊 
11  { 12         int v=edge[i].to;//搜索到的點 
13         if(!vis[v])//若是未搜索過即未入棧 
14  { 15             dfs(v);//繼續以此點進行深搜 
16             low[u]=min(low[u],low[v]);//更新low值,此邊爲樹枝邊因此比較u此時的
17         }                           // low值(未更新時就是其dfn值)和v的low值
18         else 
19             if(insta[v]==true)//若是搜索過且在棧中,說明此邊爲後向邊或棧中橫叉邊
20  { 21                 low[u]=min(low[u],dfn[v]);//更新low值,比較u此時的low值和v的dfn值 
22  } 23  } 24 
25     if(low[u]==dfn[u])//相等說明找到一個強連通份量 
26  { 27         while(top>0&&stack[top]!=u)//開始退棧一直退到 u爲止 
28  { 29             top--; 30             insta[stack[top]]=false; 31  } 32  } 33 }
View Code

 例題:

poj2186 - Popular Cows

題目大意:題目大意是:在一個牧羣中,有N個奶牛,給定M對關係(A,B)表示A仰慕B,並且仰慕關係有傳遞性,問被全部奶牛(除了本身)仰慕的奶牛個數

解題思路:找出全部的連通份量,若是隻有一個連通份量的出度爲0,那麼輸出那個連通份量的點的個數便可,若是不惟一就輸出0
由於連通份量的出度有兩個的話,那麼確定至少存在一頭牛不仰慕另一頭牛,因此咱們至少保證要出度爲0的連通份量惟一

解題思路:

一、用Tarjan求雙連通份量而後縮成點。這些點會造成一棵樹。

二、求樹上的節點有多少個出度爲零,若是有一個就輸出那個點裏包含的全部點(由於是縮點出來的樹)。

 

注意:

一、給出的圖會有不連通的可能,若是那樣確定輸出零。由於不連通確定不會有全部其餘牛認爲某隻牛很牛的狀況出現。

二、若是縮點後有多個出度爲零的點,那麼輸出零。由於這樣圖雖然聯通了,可是仍是不會出現全部其餘牛認爲某隻牛很牛的狀況(本身畫一下就知道啦)。

求強連通份量主要是爲了簡化圖的構造,若是份量外的一個點能到達份量內的其中一個點,那麼它一定能到達份量內的全部點,因此某種程度上,強連通份量能夠簡化成一個點。

#include <stdio.h> #include <string.h>
const int MAXN = 10005; const int MAXM = 100005; struct node { int to,next; } edge[MAXM]; int n,m,head[MAXN],dfn[MAXN],low[MAXN],stack1[MAXN],num[MAXN],du[MAXN],vis[MAXN],cnt,time,top,cut; void init() { memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(head,-1,sizeof(head)); memset(vis,0,sizeof(vis)); memset(num,0,sizeof(num)); memset(du,0,sizeof(du)); cnt=0; time=1; top=0; cut=0; } void addedge(int u,int v) { edge[cnt].to=v; edge[cnt].next=head[u]; head[u]=cnt; cnt++; } int min(int a,int b) { if(a>b)a=b; return a; } void dfs(int u,int fa) { dfn[u]=time; low[u]=time; time++; vis[u]=1; stack1[top]=u; top++; for(int i=head[u]; i!=-1; i=edge[i].next) { int v=edge[i].to; if(!vis[v]) { dfs(v,u); low[u]=min(low[u],low[v]); } else if(vis[v]) { low[u]=min(low[u],dfn[v]); } } if(low[u]==dfn[u]) { cut++; while(top>0&&stack1[top]!=u) { top--; vis[stack1[top]]=2; num[stack1[top]]=cut; } } } int main() { int i,u,v; while(scanf("%d%d",&n,&m)!=EOF) { init(); for(i=0; i<m; i++) { scanf("%d%d",&u,&v); addedge(u,v); } for(int i=1; i<=n; i++) { if(!vis[i]) { dfs(i,0); } } for(i=1; i<=n; i++) { for(int j=head[i]; j!=-1; j=edge[j].next) { if(num[i]!=num[edge[j].to]) { du[num[i]]++; } } } int sum=0,x; for(i=1; i<=cut; i++) { if(!du[i]) { sum++; x=i; } } if(sum==1) { sum=0; for(i=1; i<=n; i++) { if(num[i]==x) { sum++; } } printf("%d\n",sum); } else { puts("0"); } } return 0; }
View Code
相關文章
相關標籤/搜索