tarjan演算法強連通分量的正確性解釋+錯誤更新方法的解釋!!!+hdu1269
- 2020 年 3 月 28 日
- 筆記
題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=1269
以下內容為原創,轉載請聲明。
強連通分量SCC(Strongly Connected Component):一個圖的子圖,如果任何兩個點都互相可達且滿足最大性,該子圖就是原圖的SCC。
對於有向圖的連通性,tarjan可以說是十分牛逼了,由於tarjan只需要一次dfs就能判斷有向圖中所有的SCC,所以他的複雜度是O(|V|+|E|),也就是把每條邊和每個點都會檢索一遍,是在線性時間能處理出所有的SCC的!下面簡單說明求強連通分量的tarjan演算法的過程以及正確性。
基礎知識:
①、經dfs處理過的點的low值相同的一定屬於同一個SCC,這個SCC中第一個被dfs訪問的點也就是dfn=low的點一定就是這個SCC中每個點的low值,顯而易見。
②、在dfs過程中,dfs進行最深處一定會進入某一個SCC,(儘管在過程中可能會進入其他SCC),下面我會解釋
③、標記SCC Number的過程可以用棧實現,因為dfs的過程就是遞歸,遞歸和棧非常相似
tarjan演算法的流程:將每個訪問的點入棧,更新low值,直到到達一個點,這個點low值等於dfn值,說明這個點一定是一個SCC的祖先,也就是第一個訪問的點,此時這個點一定在棧的底部!!因為是通過dfs退出多次而不彈棧才得到這個low=dfn。我們只要不斷彈出l這個SCC的點並標記就可以,直到彈到棧底。我們先看基礎知識(2),上圖中在訪問3的時候進入了4、5結點,也就是其他SCC,到了5的時候,由於low[5]=dfn[5]所以此時彈棧,發現就只有一個5屬於這個SCC,4也是同樣的過程,直到到了3,進入另一個分支6,這就是我基礎知識(2)中的一定會在最終進入一個SCC而且棧底元素就是這個SCC的祖結點。
關於強連通分量處理回退邊的方式,我的解釋是這樣的:
當一個點已經標記過dfn之後,而且這個點在之前沒有標記過SCC(因為在dfs過程中會進入並處理其他SCC,若此時有邊相連就無需更新,因為回退邊所到點的low值一定小於該點的low)就用
①、low[u]=min(low[v],dfn[v]) ②、low[u]=min(low[v],low[u])
來更新low值,其實對於雙連通分量來說,這兩種更新方式的結果是一樣的,但是第一種方法在原理上是錯誤的!!!!! 我來舉一個例子,又是下面這張圖,
對於第一種我們得出的結果是下面第一張圖,而對於第二張我們得到的結果是下面第二張圖,我們可以發現,根據tarjan演算法的思想,處於同一個強連通分量中的點low值應該是相同的,但是第一種做法中的點的low值是不同的,但是他們得到的結果都是一樣的,都是圖是強連通的,為什麼呢?
下面我將證明第一種做法得出正確結果的原因:
由於對於4,5號節點來說,他們的low值為3的時候,都與他們的dfn值不相同(dfn[4]=4&dfn[5]=5),所以他們就會一直在棧中,而棧底也一定是1號結點。4,5號結點沒有機會彈出,直到dfs return到1號結點,此時才會彈棧,一直到彈出1號結點為止,這就把棧中的結點4,5彈出了,但是事實上他們的low值是更新錯誤的。儘管是錯誤的,但是中途dfs在訪問其他SCC的時候(如果有的話,可以參考最上面那張圖)已經把其他SCC的點都標記了,此時棧中剩下的只有這一個SCC!!所以最終的結果是相同的,但是!!這種更新方法是錯誤的!!因為在之後不能通過他們的不同的low值數量去查看SCC的數量,只能從每一次彈棧中給出的SCCNUMBER中得到SCC的資訊。而且,這種做法跟tarjan的演算法中“每個SCC”的low值是相同的是矛盾的。
看完我的文章能不能支援一下呢
hdu1269程式碼如下:
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef unsigned int ui; 4 typedef long long ll; 5 typedef unsigned long long ull; 6 #define pf printf 7 #define mem(a,b) memset(a,b,sizeof(a)) 8 #define prime1 1e9+7 9 #define prime2 1e9+9 10 #define pi 3.14159265 11 #define lson l,mid,rt<<1 12 #define rson mid+1,r,rt<<1|1 13 #define scand(x) scanf("%llf",&x) 14 #define f(i,a,b) for(int i=a;i<=b;i++) 15 #define scan(a) scanf("%d",&a) 16 #define mp(a,b) make_pair((a),(b)) 17 #define P pair<int,int> 18 #define dbg(args) cout<<#args<<":"<<args<<endl; 19 #define inf 0x3f3f3f3f 20 inline int read(){ 21 int ans=0,w=1; 22 char ch=getchar(); 23 while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();} 24 while(isdigit(ch))ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar(); 25 return ans*w; 26 } 27 const int maxn=1e5+10; 28 int n,m,t; 29 int head[maxn],nxt[maxn],low[maxn],sccno[maxn],dfn[maxn],id; 30 struct node{ 31 int u,v; 32 }p[maxn*10]; 33 int e=0; 34 int cnt; 35 stack<int> sk; 36 void addedge(int u,int v) 37 { 38 p[e].u=u; 39 p[e].v=v; 40 nxt[e]=head[u]; 41 head[u]=e++; 42 } 43 void tarjan(int u) 44 { 45 low[u]=dfn[u]=++id; 46 sk.push(u);//將結點編號壓棧 47 for(int i=head[u];~i;i=nxt[i]) 48 { 49 int v=p[i].v; 50 if(!dfn[v]) 51 { 52 tarjan(v); 53 low[u]=min(low[u],low[v]); 54 } 55 else if(!sccno[v])//回退邊且邊終點沒有標記SCC 56 low[u]=min(low[u],low[v]); 57 } 58 if(low[u]==dfn[u]) 59 { 60 cnt++;//連通分量的數量增加,對每個點這個if語句只執行一次 61 while(1) 62 { 63 int v=sk.top(); 64 sk.pop(); 65 sccno[v]=cnt; 66 if(u==v)break; 67 } 68 } 69 } 70 int main() 71 { 72 freopen("input.txt","r",stdin); 73 //freopen("output.txt","w",stdout); 74 std::ios::sync_with_stdio(false); 75 while(scanf("%d%d",&n,&m)==2) 76 { 77 if(n==0&&m==0)break; 78 e=0;cnt=0; 79 mem(head,-1);mem(nxt,-1); 80 while(!sk.empty())sk.pop(); 81 int x,y; 82 f(i,1,m) 83 { 84 x=read(); 85 y=read(); 86 addedge(x,y); 87 } 88 mem(dfn,0); 89 id=0; 90 mem(sccno,0); 91 mem(low,0); 92 f(i,1,n) 93 { 94 if(!dfn[i]) 95 tarjan(i); 96 } 97 f(i,1,n)dbg(low[i]); 98 if(cnt==1)pf("Yesn"); 99 else pf("Non"); 100 } 101 }