部份內容引自https://www.cnblogs.com/stxy-ferryman/p/7779347.htmlhtml
Tarjan算法不是一個算法而是一類算法算法
1.求取強連通份量數組
強連通份量————有向圖的強連通子圖優化
tarjan算法基於dfs,利用棧的思想,把下面全部的點都遍歷完畢後,所能連接的最小祖先節點(可能沒有),就是要尋找的強連通份量htm
因此咱們須要dfn數組存儲dfs的遍歷順序,low數組存儲這個節點後全部的子孫節點所能到達的最小節點(dfn最小)值blog
爲了可以得知構成這個強連通份量的全部的點,能夠利用棧去記錄,由於退的時候,確定是退到最頭上(若是有額外的分支,那麼以前確定早就退棧了)繼承
當咱們遍歷的時候初始化時dfn = low = idx這個初始化意思很好懂,若是沒有後繼節點,值就是這個遞歸
接下來咱們可能會面對三種點get
·沒有遍歷過的,咱們就遞歸tarjan遍歷,而後優化low[u] = min(low[u],low[v])class
·咱們遍歷過了,這個點還在棧中,那就表明這個點u能夠到達,因此咱們更新的時候,low[u] = min(low[u],dfn[v])_____你要知道此時的low[v]是取決於如今的low[u]的由於v在棧中,因此u是v的後繼節點,這是一條返祖邊
·咱們遍歷過了,這個點不在棧中了,證實這個點經歷了一次退棧,造成了一次聯通份量,可是不包括u,由於這個點不能到達u,若是這個點能夠到達u的話,u又能夠到達這個點,那麼就不會退棧了(這裏的到達都是要通過子孫節點的),因此對於這樣的點,沒必要考慮任何問題
因此直到遍歷完全部的子孫節點咱們就能夠進行退棧的操做了,那些獨立的聯通份量的標誌就是
low == dfn
意思就是對於節點u,其子孫節點所能到達的最大節點就是u,也就是造成了一個迴路,環,並且能夠保證這個環是最大的
因此說了這麼多,以上的算法思想用於求取一個圖的強連通份量——最後進行color染色處理
void tarjan(int u,int fa) { dfn[u] = low[u] = ++index; stk[s_cnt++] = u; instk[u] = true; for(int i = id[u];~i;i = e[i].pre) { int v = e[i].to; if(!dfn[v]) { tarjan(v,u); low[u] = min(low[u],low[v]); } else if(instk[v] == true) { low[u] = min(low[u],dfn[v]); } } if(dfn[u] == low[u]) { col++; while(s_cnt > 0 && stk[s_cnt] != u) { s_cnt --; color[stk[s_cnt]] = col; instk[stk[s_cnt]] = false; } } }
2.tarjan縮點
利用tarjan算法能夠把一個圖變成單向無環圖
這個繼承自強連通份量,對於每個強連通份量,咱們可以看出一個超級點,這個超級點的內部能夠互相到達,而後根據這個圖所表示的含義,一般要去計算超級點的度,最後輸出知足題意的超級點內全部的點
和上面的代碼一致
3·求割點和橋(割邊)
割點:去掉這個點(和這個點外射的全部邊),把一個連通圖變成多個連通份量
割邊:一樣的道理,去掉這條邊,把一個連通圖變成多個連通份量
這時候咱們要判斷什麼時候是一個割點
1.當前節點是根節點——從這個節點開始的dfs,因此若是這個點有兩個子樹,那麼就是一個割點
2.任意節點,若是low[v] >= dfn[u],表示u的這個子孫可以到達的最小祖先節點是比u小的,因此u是子孫v鏈接祖先的關鍵點
void tarjan_gedian(int u,int fa) { int son = 0; dfn[u] = low[u] = ++index; for(int i = id[u];~i;i = e[i].pre) { int v = e[i].to; if(!dfn[v]) { tarjan(v,u); son++; low[u] = min(low[u],low[v]); if(u != root && low[v] >= dfn[u]) { cut_point[u] = 1; } else if(u == root && son > 1) { cut_point[u] = 1; } } else if(v != fa)對於割點u這是一條迴路,且u割去以後毫無影響,因此忽略這樣的兩點間迴路狀況 { low[u] = min(low[u],dfn[v]); } } }