「tarjan陪伴強聯通份量 生成樹完成後思路才閃光 歐拉跑過的七橋古塘 讓你 心馳神往」----《膜你抄》
引用來自度孃的一句話:html
「有向圖強連通份量:在有向圖G中,若是兩個頂點vi,vj間(vi>vj)有一條從vi到vj的有向路徑,同時還有一條從vj到vi的有向路徑,則稱兩個頂點強連通(strongly connected)。
若是有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。有向圖的極大強連通子圖,稱爲強連通份量(strongly connected components)。」數組
一臉懵逼......不過倒也不難理解。spa
反正就是在圖中找到一個最大的圖,使這個圖中每一個兩點都可以互相到達。這個最大的圖稱爲強連通份量,同時一個點也屬於強連通份量。code
如圖中強連通份量有三個:1-2-3,4,5component
噫......固然,經過肉眼能夠很直觀地看出1-2-3是一組強連通份量,但很遺憾,機器並無眼睛,因此該怎麼判斷強連通份量呢?htm
若是還是上面那張圖,咱們對它進行dfs遍歷。blog
能夠注意到紅邊很是特別,由於若是按照遍歷時間來分類的話,其餘邊都指向在本身以後被遍歷到的點,而紅邊指向的則是比本身先被遍歷到的點。get
若是存在這麼一條邊,那麼咱們能夠yy一下,emmmm.......class
從一個點出發,一直向下遍歷,而後忽得找到一個點,那個點居然有條指回這一個點的邊!遍歷
那麼想必這個點可以從自身出發再回到自身
想必這個點和其餘向下遍歷的該路徑上的全部點構成了一個環,
想必這個環上的全部點都是強聯通的。
但只是強聯通啊,咱們須要求的但是強連通份量啊......
那怎麼辦呢?
咱們仍是yy出那棵dfs樹
不妨想一下,何時一個點和他的全部子孫節點中的一部分構成強連通份量?
他的子孫再也沒有指向他的祖先的邊,卻有指向他本身的邊
由於只要他的子孫節點有指向祖先的邊,顯然能夠構成一個更大的強聯通圖。
好比說圖中紅色爲強連通份量,而藍色只是強聯通圖
那麼咱們只須要知道這個點u下面的全部子節點有沒有連着這個點的祖先就好了。
但彷佛還有一個問題啊......
咱們怎麼知道這個點u它下面的全部子節點必定是都與他強聯通的呢?
這彷佛是不對的,這個點u之下的全部點不必定都強聯通
那麼怎麼在退回到這個點的時候,知道全部和這個點u構成強連通份量的點呢?
開個棧記錄就好了
什麼?!這麼簡單?
沒錯~就是這麼簡單~
若是在這個點以後被遍歷到的點已經能與其下面的一部分點(也可能就只有他一個點)已經構成強連通份量,即它已是最大的。
那麼把它們一塊兒從棧裏彈出來就好了。
因此最後處理到點u時若是u的子孫沒有指向其祖先的邊,那麼它以後的點確定都已經處理好了,一個常見的思想,能夠理解一下。
因此就能夠保證棧裏留下來u後的點都是能與它構成強連通份量的。
彷佛作法已經明瞭了,用程序應該怎麼實現呢?
因此爲了實現上面的操做,咱們須要一些輔助數組
(1)、dfn[ ],表示這個點在dfs時是第幾個被搜到的。
(2)、low[ ],表示這個點以及其子孫節點連的全部點中dfn最小的值
(3)、stack[ ],表示當前全部可能能構成是強連通份量的點。
(4)、vis[ ],表示一個點是否在stack[ ]數組中。
那麼按照之上的思路,咱們來考慮這幾個數組的用處以及tarjan的過程。
(1)、首先初始化dfn[u]=low[u]=第幾個被dfs到
dfn能夠理解,但爲何low也要這麼作呢?
由於low的定義如上,也就是說若是沒有子孫與u的祖先相連的話,dfn[u]必定是它和它的全部子孫中dfn最小的(由於它的全部子孫必定比他後搜到)。
(2)、將u存入stack[ ]中,並將vis[u]設爲true
stack[ ]有什麼用?
若是u在stack中,u以後的全部點在u被回溯到時u和棧中全部在它以後的點都構成強連通份量。
(3)、遍歷u的每個能到的點,若是這個點dfn[ ]爲0,即仍未訪問過,那麼就對點v進行dfs,而後low[u]=min{low[u],low[v]}
low[ ]有什麼用?
應該能看出來吧,就是記錄一個點它最大能連通到哪一個祖先節點(固然包括本身)
若是遍歷到的這個點已經被遍歷到了,那麼看它當前有沒有在stack[ ]裏,若是有那麼low[u]=min{low[u],low[v]}
若是已經被彈掉了,說明不管如何這個點也不能與u構成強連通份量,由於它不能到達u
若是還在棧裏,說明這個點確定能到達u,一樣u能到達他,他倆強聯通。
(4)、假設咱們已經dfs完了u的全部的子樹那麼以後不管咱們再怎麼dfs,u點的low值已經不會再變了。
那麼若是dfn[u]=low[u]這說明了什麼呢?
再結合一下dfn和low的定義來看看吧
dfn表示u點被dfs到的時間,low表示u和u全部的子樹所能到達的點中dfn最小的。
這說明了u點及u點之下的全部子節點沒有邊是指向u的祖先的了,即咱們以前說的u點與它的子孫節點構成了一個最大的強連通圖即強連通份量
此時咱們獲得了一個強連通份量,把全部的u點之後壓入棧中的點和u點一併彈出,將它們的vis置爲false,若有須要也能夠給它們打上相同標記(同一個數字)
對了,tarjan一遍不能搜完全部的點,由於存在孤立點或者其餘
因此咱們要對一趟跑下來尚未被訪問到的點繼續跑tarjan
怎麼知道這個點有沒有被訪問呢?
看看它的dfn是否爲0!
這看起來彷佛是o(\(n^2\))的複雜度,但其實均攤下來每一個點只會被遍歷一遍
因此tarjan的複雜度爲o(\(n\))。