1. 程式人生 > >對求有向圖強連通分量的tarjan算法原理的一點理解

對求有向圖強連通分量的tarjan算法原理的一點理解

深度優先 含義 出現 組合 分支 ron 滿足 根節點 節點和

先簡單敘述一下tarjan算法的執行過程(其他諸如偽代碼之類的相關細節可以自己網上搜索,這裏就不重復貼出了):

用到兩類數組:

dfs[]:DFS過程中給定節點的深度優先數,即該節點在DFS中被訪問的次序

low[]:從給定節點回溯時,節點的low值為從節點在DFS樹中的子樹中的節點可以回溯到的棧中DFS值最小的節點的dfs值

一個數據結構:棧,用於確定強連通分量

執行過程:對有向圖進行深度優先搜索,每抵達一個新節點A就把該節點A入棧,並初始化dfs[A],然後將low[A]初始化為dfs[A],隨後考察該節點A通過邊可達的所有節點。若其中一個節點未訪問,則對其遞歸DFS,遍歷結束從該節點退出回溯至A時,用該節點low值(已確定不會再改變)更新A的low值,若其中一個節點已訪問但不在棧中,則跳過,若已訪問且在棧中,則用該節點dfs值跟新A節點low值。當通過邊與A相連的所有節點都考察完畢了,A的low值就最終確定了,不會再變,此時在從A回溯至DFS中A的前驅節點前檢查low[A]是否等於DFS[A],若是不斷彈棧直到把A彈出為止,彈出的節點組成一個強連通分量,若不是什麽都不做,回溯至前驅節點。

要註意的是,算法執行過程中按DFS遍歷次序入棧,退棧不影響棧中各節點的DFS訪問順序和它們在棧中位置關系的邏輯關系,所以任何時刻,若棧中B在棧中A之上則dfs[B]>dfs[A],反之也真。

還有就是從tarjan算法偽代碼不難看出,當從節點A回溯時必有dfs[A]>=low[A].

並且算法中給定節點僅入棧一次出棧一次訪問一次,回溯過給定節點就不會再次回溯,回溯至給定節點時節點的low值就已確定,直至算法結束都不會改變

結合這幾件簡單事實可以證明下面三個命題,學習tarjan算法的關鍵就是理解回溯到給定節點A時對條件dfs[A]==low[A]的測試的含義以及測試成功後所執行的一系列彈棧操作,以下三個命題有助於理解

命題1:在tarjan算法中回溯到一給定節點A時,若A節點為通過DFS所到達的深度優先樹中A節點所在的強連通分量中的第一個被訪問的節點(根節點),則棧中A節點之上的所有節點(包括A節點)必為A節點所在強連通分量的所有節點

證明:事實上,取棧中A之上的某節點B,若節點B不屬於A所在強連通分量,則B必然屬於另外一個不同的強連通分量L,該強連通分量有根節點B‘,B‘不可能是從未被訪問的節點,否則由B為不同的強連通分量的根節點知B尚未被訪問故而不會被壓入棧中,矛盾。B‘也不可能是已被訪問壓入棧中但隨後又被彈出的節點,若不然當壓入B‘後必然會回溯至B‘,此時檢測到dfs[B‘]==low[B‘],於是從棧中彈出B‘和B‘以上全部節點,由B‘為不同強連通分量根節點知B在B‘後壓棧,故前述彈棧操作結束後無論如何B不會在棧中,根據假設針對B‘的彈棧操作在回溯至A節點之前發生,故回溯至A節點時B節點已不存在於棧中矛盾。這樣B‘必然在棧中,顯然B‘不為A,B‘也不可能在棧中A的位置之下,若不然由B‘為L根節點和B屬於L知,有從B到達B‘的路徑,但棧中B位於A之上,這樣B位於A的子樹中(若不然註意棧中B位於A之下即A比B先訪問,故dfs[B]>dfs[A],這樣當訪問B時A已從棧彈出(這是因為B位於DFS樹中A節點右側的A的祖先節點的子女中)),又如前所述有從B到達B‘的路徑且dfs[B‘]<dfs[A](因為棧中B‘在A之下)(同時要註意A在B‘子樹中,理由同下,若不然訪問A時B‘已經因回溯至B‘時dfs[B‘]==low[B‘]而被彈出棧,這樣回溯至A時B‘不在棧中,矛盾’),故low[A]<dfs[A]矛盾。於是我們證明了B‘必然位於棧中A之上,這樣當回溯至B‘時由於dfs[B‘]=low[B‘](B‘為L的根節點),B‘及B‘之上的所有節點都會被彈出,而B必然在B‘後壓棧,這樣前述彈棧操作結束後B已不在棧中,此後我們才回溯至A,此時B已不在棧中,矛盾。這樣就證明了回溯到A時棧中A之上的任一節點必屬於A所在強連通分量。此外,當回溯到A時,之前入棧並已被彈出的任一節點C不可能屬於A所在強連通分量,若不然,考慮C入棧後回溯到C的時刻,此時由於C屬於A所在強連通分量,所以存在A到C的路徑,又由於A為A所在強連通分量的根節點,且A和C並非同一節點(註意算法中tarjan算法中一個給定節點僅入棧一次,出棧一次,而回溯到A時C已彈出,此時如A==C,則C在彈出後又入棧回到棧中,矛盾),所以C必在A被訪問後訪問(即dfs[C]>dfs[A]),即必在A被壓棧之後壓棧,這樣當回溯到C時A必在棧中且位於C之下,由於C屬於A所在強連通分量,所以存在C到A的路徑。當回溯到C時,可以斷言必有dfs[C]!=low[C],若不然C成為強連通分量M的根節點,由於A為A所在強連通分量根節點而A不等於C,故M和A所在強連通分量為不同的強連通分量,註意到C屬於A所在強連通分量,這樣A所在強連通分量可以和M合並組合成一個更大的強連通分支,這和M以及A所在強連通分量為極大強連通子圖矛盾,所以就證明必有dfs[C]!=low[C]。於是當回溯至C時,不會對C執行彈出C及棧中其上節點的彈棧操作。然後可以斷言,在回溯至C後和回溯至A前不可能有針對棧中C和A之間(不包括A和C)的節點的彈棧操作,若不然取這些彈棧操作中最早發生的一次,此時將會回溯至棧中C和A之間的節點D,此時應有dfs[D]=low[D],應對D執行該彈棧操作。註意到D在棧中位於A之上,dfs[D]>dfs[A],D比A後訪問,這樣D必位於DFS樹中節點A的子樹中,否則由於D後訪問,D就位於A右邊的A的祖先節點的子女中,這樣訪問D時A就已經因之前回溯到A時判斷出dfs[A]=low[A]而彈出了,故不在棧中,這樣回溯至D時A仍不在棧中(給定節點只會進棧一次),矛盾(回溯至D在回溯至A之前發生,此時A仍在棧中)於是D位於DFS樹中節點A的子樹中,此外C位於DFS樹中節點D的子樹中,理由同上(根據假設回溯至D時dfs[D]==low[D],而D在棧中位於C之下,C後訪問,若C不在D的子樹中,訪問C時D已彈出,回溯至C時棧中無D,矛盾(回溯至C在回溯至D之前發生,此時D仍在棧中))。這樣D的子樹中的節點C有一條指向D的祖先節點A的路徑,在棧中A在D之下,所以回溯至D時low[D]<dfs[D]這和dfs[D]=low[D]矛盾,這樣就證明了在回溯至C後和回溯至A前不可能有針對棧中C和A之間(不包括A和C)的節點的彈棧操作。這樣回溯至C之後,回溯至A之前,C不可能被彈出,故回溯至A時C仍在棧中,這和回溯至A時C已被彈出的假設矛盾,這就證明了回溯到A時,之前入棧並已被彈出的任一節點C不可能屬於A所在強連通分量。另外,回溯到A時從未被訪問的節點也不可能屬於A所在強連通分量,若不然就存在一條A到未被訪問節點的路徑,根據DFS搜索順序,回溯到A時未被訪問的節點實際上已經訪問過了,矛盾。這樣就證明了A所在的強連通分量中所有節點都位於棧內,下面可以斷言棧中位於A之下的所有節點不可能屬於A所在的強連通分量,證明很簡單,若不然存在A之下的某節點E屬於A所在的強連通分量,註意dfs[E]<dfs[A],這樣E比A先訪問,而E屬於A所在的強連通分量,故A不可能是DFS中A所在的強連通分量被訪問的第一個節點,矛盾。這樣就證明了棧中A及A之上的所有節點構成了A所在的強連通分量的全部節點,證明完畢。

命題:2 在tarjan算法中回溯到一給定節點A時,若dfs[A](A在DFS中被訪問的次序)==low[A](A的子樹中的節點所能回溯到的棧中DFS值最小的節點的DFS值)則該節點必為通過DFS所到達的深度優先樹中該節點所在的強連通分量中的第一個被訪問的節點(根節點)

證明:使用反證法,若該節點不為深度優先樹中該節點所在的強連通分量中被訪問的第一個節點,則記深度優先樹中該節點強連通分量中被訪問的第一個節點為F,F不等於A。由命題3證明知F所在的強連通的分量(即A所在強連通分量)除F以外的所有節點一定都位於F的子樹中,而A在A所在強連通分量中,A不等於F,所以A在F的子樹中。在DFS過程中回溯到A時,由於F比A先訪問,所以在棧中F位於A之下,dfs[F]<dfs[A],由於dfs[A]==low[A],所以回溯到A時會對A執行彈棧操作,此後A再也不會出現在棧中。當回溯到F時,由於F為深度優先樹中節點F所在強連通分量((即A所在強連通分量))中被訪問的第一個節點,所以由命題1,此時棧中F節點之上的所有節點(包括F節點)必為A節點所在強連通分量(即F所在強連通分量)的所有節點,但是位於A節點所在強連通分量中的節點A不在棧中,矛盾,這就證明了命題2

命題3:在tarjan算法中回溯到一給定節點A時,若A是它所在的強連通分量在深度優先樹中被第一個訪問的節點,則必有dfs[A]==low[A]

證明:首先,回溯至節點A時,由於A為它所在強連通分量的根節點,而A所在的強連通的分量除A以外的所有節點一定都位於A的子樹中(假若有節點B(A不等於B)位於A所在的強連通分量,而節點B不在A的子樹中,由於存在從A到B的路徑,所以當訪問A所在的強連通分量在DFS樹中被訪問的第一個節點A後,根據DFS的搜索規則,在回溯至A之前一定會訪問B,而訪問A後回溯至A前一直在訪問A的子樹,在DFS中訪問A的子樹時不可能跨越子樹去訪問不在子樹中的節點B),所以A的子樹中有一個屬於A所在強連通分量的節點,存在該節點到A的一條路徑,由於回溯至A時A在棧中,所以low[A]<=dfs[A].我們來證明回溯至A時必有dfs[A]==low[A],如若不然,回溯至A時有low[A]<dfs[A],此時棧中A之上的所有節點C均滿足low[C]<dfs[C](否則low[C]==dfs[C],這樣回溯至A前回溯至C時,C被出棧,回溯至A時節點C不在棧中,矛盾。還要註意的是回溯至一給定節點時該節點的low值已確定,以後不會再改變),且根據DFS訪問順序棧中A之上的所有節點C都已經被回溯過了(而且已經回溯過的節點不會再次被訪問和回溯),回溯至A時low[A]<dfs[A],這樣不會針對A執行彈棧操作,於是對A的回溯結束後,棧中A及A之上的所有節點都會保留。另外,棧中A之下必然存在節點D,使得當回溯至D時有low[D]==dfs[D],如若不然當回溯至棧底節點F(棧中DFS值最小節點)時,仍有low[F]<dfs[F],從而存在從F的子樹中某節點到棧中dfs值比dfs[F]還小的節點的路徑,而棧中dfs值比dfs[F]還小的節點根本不存在,矛盾。於是我們知道,對A的回溯結束後,必然會回溯到棧中A之下的某節點,該節點low值==dfs值,我們取最早回溯到的這樣的節點E,當回溯到E時,回溯到A時棧中A及A之上的節點仍然位於棧中(這是因為對A的回溯結束後,棧中A及A之上節點仍在棧中,此後由於這些節點不會被再次回溯到,所以只有第一次對棧中A之下的節點執行彈棧操作時這些節點才會被彈出,在此之前,結束對A的回溯之後,這些節點都會被保留),而回溯到E時有low[E]==dfs[E],根據之前證明的命題:2,E為通過DFS所到達的深度優先樹中E所在的強連通分量中的第一個被訪問的節點(根節點),然後由命題1我們得出棧中E節點之上的所有節點(包括E節點)必為E節點所在強連通分量的所有節點,回溯到E時棧中A及A之上的節點仍然位於棧中,在棧中E位於A之下,這樣棧中E節點之上所有節點包括了棧中A及A節點之上所有節點,於是A在E所在的強連通分量中,同時A又是A所在的強連通分量在DFS樹中的根節點,於是我們把E所在強連通分量和A所在強連通分量合並得到強連通分支G,E所在強連通分量和A所在強連通分量都被包含於G中,這和它們是極大強連通子圖即強連通分量矛盾,這樣命題3證畢

PS:博主水平有限,如果以上證明存在疏漏或錯誤懇請指正,謝謝.Tarjan算法簡單易實現,但是原理還是比較復雜的,不容易理解,在這裏向圖靈獎獲得者Tarjan致敬

對求有向圖強連通分量的tarjan算法原理的一點理解