Tarjan演算法初步

2022-04-12 02:02:35 字數 3334 閱讀 9443

一、前置知識:

強連通分量:有向圖強連通分量:在有向圖g中,如果兩個頂點vi,vj間(vi>vj)有一條從vi到vj的有向路徑,同時還有一條從vj到vi的有向路徑,則稱兩個頂點強連通(strongly connected)。如果有向圖g的每兩個頂點都強連通,稱g是乙個強連通圖。有向圖的極大(看清是極大,不是最大)強連通子圖,稱為強連通分量(strongly connected components)。乙個點x,若沒有點與它強連通,則它自己也是乙個強連通分量。

二、演算法簡述

tarjan演算法是乙個主要用於求有向圖的強連通分量或無向圖的環的演算法。(當然也有很多擴充套件,與其他一些神奇的演算法搭配可能會碰撞出奇妙的火花)

(無向圖的極大連通子圖就不叫強連通分量了,叫連通分量。實際上求無向圖的連通分量用bfs就行了(畢竟無向圖沒有方向,只要能到達,就是連通,也沒有強連通這一說))

三、原理: 

想象乙個情景:從乙個點u出發,一直向下遍歷,然後忽得找到乙個點,那個點x竟然有條指回點u的邊!

那麼想必這個點u能夠從自身出發再回到自身

想必這個點u和其他向下遍歷的該路徑上的所有點構成了乙個環,

想必這個環上的所有點都是強聯通的。

但只是強聯通啊,我們需要求的可是強連通分量啊......怎麼在退回到這個點的時候,知道所有和這個點u構成強連通分量的點呢?

開個棧記錄就行了。我們建乙個棧,保證回溯到u時棧中u及u上面的點組成乙個強連通分量,然後把它們彈出、記錄就好了。

似乎做法已經明了了,用程式應該怎麼實現呢?

四、程式實現:

首先需要介紹一些輔助陣列

(1)、dfn[ ],表示這個點在dfs時是第幾個被搜到的。

(2)、low[ ],表示這個點以及其子孫節點連的這個點及其祖先中dfn最小的值

(3)、stack[ ],表示當前所有可能能構成強連通分量的點。

(4)、vis[ ],表示乙個點是否在stack[ ]陣列中。

那麼按照之上的思路,我們來考慮這幾個陣列的用處以及演算法的具體過程。

假設現在開始遍歷點u:

首先初始化dfn[u]=low[u]=第幾個被dfs到

dfn可以理解,但為什麼low也要這麼做呢?

因為low的定義如上,也就是說如果沒有子孫與u的祖先相連的話,dfn[u]一定是它和它的所有子孫中dfn最小的(因為它的所有子孫一定比他後搜到)。

將u存入stack[ ]中,並將vis[u]設為true

stack[ ]有什麼用?

如果u在stack中,u之後的所有點在u被回溯到時u和棧中所有在它之後的點都構成強連通分量。(也就是上文中所說的開個棧記錄)

遍歷u的每乙個能到的點,如果這個點dfn[ ]為0,即仍未訪問過,那麼就對點v進行dfs,然後low[u]=min        

low[ ]有什麼用?        應該能看出來吧,就是記錄乙個點它最大能連通到哪個祖先節點(當然包括自己)

如果從u遍歷這個點之前這個點就被遍歷到了,那麼看它當前有沒有在stack[ ]裡,如果有(要麼這個點是u的祖先,要麼這個點與u的某個祖先強連通,反正這個點能到達u),說明這個點肯定能到達u,同樣u能到達他,他倆強聯通,那麼low[u]=min

如果已經被彈掉了,說明無論如何這個點也不能與u構成強連通分量,因為它不能到達u(當處理強連通分量時才將元素彈出棧。處理包含這個點的強連通分量時沒有處理掉u,就說明u不在它的強連通分量裡)

假設我們已經dfs完了u的所有的子樹,那麼之後無論我們再怎麼dfs,u點的low值已經不會再變了。 

那麼如果dfn[u]=low[u]這說明了什麼呢?

再結合一下dfn和low的定義來看看吧

dfn表示u點被dfs到的時間,low表示u和u所有的子樹所能到達的u的祖先中dfn最小的。

這說明了u點及u點之下的所有子節點沒有邊是指向u的祖先的了,即我們之前說的u點與它的還在棧中的子孫節點構成了乙個最大的強連通圖即強連通分量

此時我們得到了乙個強連通分量,把所有的u點以後壓入棧中的點和u點一併彈出,將它們的vis[ ]置為false,如有需要也可以給它們染上相同顏色(後面會用到,用於縮點等等)

**大概長成這樣

對了,tarjan一遍不能搜完所有的點,因為存在孤立點或者其他

所以我們要對一趟跑下來還沒有被訪問到的點繼續跑tarjan

怎麼知道這個點有沒有被訪問呢?

看看它的dfn是否為0!

非常簡短的tarjan複雜度證明:

思考每個點最多被dfs一次,所以均攤下來複雜度是o(n)的

證畢五、擴充套件:

tarjan縮點:

1.什麼時候要用縮點

眾所周知,有向無環圖總是有著一些蜜汁優越性,因為沒有環,你可以放心的在上面跑dfs,搞dp,但如果是一張有向有環圖,事情就會變得尷尬起來了

思考一下會發現如果不打vis標記就會t飛(一直在環裡繞啊繞),但是如果打了,又不一定能保證最優解

而你一看題目卻發現顯然根據一些貪心的原則,這個環上每個點的最大貢獻都是整個環的總貢獻

這個時候縮點就顯得很有必要了,因為單個點的貢獻和整個環相同,為什麼不去把整個環縮成乙個超級點呢?

這個環只是為了好理解,事實上他應該是乙個強連通分量,顯然如果只縮掉乙個強連通圖,圖中仍然有環存在

縮點的乙個栗子

2.怎麼縮點

還記得之前tarjan裡的染色嗎?

我們只需要把同一顏色的點權加到一塊,然後把該顏色指向不同顏色的邊建好就可以了

**就不貼了,因為不同的題有不同的處理方法

無向圖tarjan求環:

每次tarjan遞迴時記錄父親節點到兒子節點走的邊的對應相反邊(因為無向圖對於一條邊用前向星存的話要插入2次),若兒子在不走這條邊的情況下仍能得到小於dfn的low,即可走另一條路徑到達父親節點的祖先,說明有乙個環。並且若干個相交的環會以乙個強連通分量的形式呈現出來。

無向圖tarjan求割點:www.cnblogs.com/collectionne/p/6847240.html

該部落格有一處low的維護操作與普通tarjan不同:low[u] = min(low[u], dfn[v]);

這裡解釋一下:若v已經被遍歷過了,這時遍歷到u發現u與v有連邊。這說明什麼?v還沒有被回溯。因為這時無向圖,既然u能到v,那v也能到u,當回溯到v時,v所能到的點必然都已經被遍歷完了。這裡剛遍歷到u,不就說明v還沒有被回溯到嘛。這樣,v不就是u的祖先了嘛。

tarjan演算法詳解

參考 tarjan演算法在強連通分量分離中運用很廣,書寫簡單,並且可以拓展到圖的割點,割邊上,十分強大 具體思路 令dfn u 表示當前點的時間戳 low u 表示當前點所能到達的點的時間戳中最小的乙個 到達點u時,將其入棧 拓展點u後代 當且僅當dfn u low u 時,棧頂元素全部出棧,此時出...

Tarjan 演算法筆記

tarjan演算法 tarjan演算法屬於圖論中的乙個演算法,主要用來求乙個圖中的強連通分量,之後就可以做很多事,比如說縮點 求雙聯通分支等。強連通 在乙個有向圖中,對於幾個點,如果它們能夠互相到達,那麼稱它們強連通。強連通分量 可以這樣理解 把乙個圖里的點分成幾坨,每坨中的點都能夠互相到達 他們強...

Tarjan演算法詳解

tarjan演算法的用途 1.求橋和割點 2.求點和邊的雙連通分量 3.求強連通 targan演算法的流程 利用dfs來遍歷圖來構建一種數型的結構 tarjan演算法的兩個核心陣列 1 對於第一種用途 tarjan演算法原理 我們從1開始遍歷,發現6,5,4的low不小於dfn 3 故3為割點 即4...