\(Tarjan\)算法
\(Tarjan\)求強連通分量
概念:
如果兩個頂點互相可達,則它們是強連通的。如果一幅有向圖中任意兩個頂點都是強連通的,則這幅有向圖也是強連通的。 強連通分量就是圖中具有連通性的一個最大子集,一般可以用來縮點,即相互到達的一堆點可以將他們有用的信息統一到一個點上去。求解強連通分量的方法一般會使用\(Tarjan\)算法。
首先我們需要學會\(dfs\)樹,定義幾種邊:
樹邊,連接\(dfs\)樹中的父節點和子節點的邊。
橫叉邊,連接\(dfs\)樹中的不在一個子樹中的兩個節點的邊。
前向邊,連接\(dfs\)樹中的一個節點連向他的子樹中深度差大於等於2,也就是不直接相連的兩點的邊。
后向邊,連接\(dfs\)樹中的一個節點連向他的祖先的邊,又叫返祖邊。
求解:
\(dfn[i]\)表示i節點的時間戳,\(low[i]\)表示i節點的所屬的強連通分量i的時間戳的最小值,也可以說是i或i的子樹能夠追溯到的最早的棧中節點的時間戳 。
發現如果在\(dfs\)樹中出現了后向邊說明后向邊連接的兩點之間的環一定是強連通的,但不一定是最大子集。
我們有個性質:如果\(low[i]\)比\(dfn[i]\)小,說明他有一條連向祖宗的后向邊,所以要保留在棧中。如果當前\(low[i]\)等於\(dfn[i]\)說明他搜索的棧中沒有可以連向他祖宗的邊,當前棧中的點一定就是\(low[棧中的點]\)等於\(dfn[i]\)的點,也可以有能連向i的反向邊。
有如果樹邊沒有被找到過,則有
\(low[u]=min(low[u],low[v]);\)也就是用v的所能連向的點的最小時間戳來更新u。
如果此點已經入棧,說明此時這是一條回邊,因此不能用\(low[v]\)更新,而應該:
\(low[u]=min(low[u],dfn[v]);\)因為,\(low[v]\)可能屬於另一個編號更小的強連通分量里,而u可以連接到v但v不一定與u相連,所以可能u、v並不屬於同一個強連通分量。但是第一種情況如果\(low[v]\)比\(low[u]\)小的話,v一定有包括u的強連通分量,
\(Tarjan\)求割點和橋
概念:
在無向連通圖中,雙連通分量分為點雙和邊雙,點雙為一個連通分量刪去任意一個點都保持連通,邊雙為刪去任意一條邊都保持連通。連接點雙的點為割點,連接邊雙的點為橋,割點和橋的性質就是刪去他們就會使原圖不連通,因為如果連通說明被連接的點雙或邊雙仍可以組成一個更大的點雙或邊雙。
求解:
我們仍可用求強連通分量的做法,用dfn和low數組。此時的low數組為能追溯到的最早的棧中的節點的時間戳,我們可以用求強連通分量的方法更新。我們每次枚舉的時候,搜索還沒有被搜索到的點,也就是原圖中多個連通塊的任意一點,然后向下搜索。對於樹邊,繼續搜索,回邊我們也用dfn更新,因為無向圖是雙向邊,樹邊同時對應着一個后向邊,因此每個點都會有個訪問父親的機會,我們用dfn更新可以防止回到自己回到祖宗而不是兒子回到祖宗。
如果u不為根節點,即對於u連向的且沒有找到過的點,先求出該點的low值,該點的low值==dfn[u],說明該點的最早能追溯到的最早的棧中節點就是u,因此刪去u之后,v無法相連到u的祖宗,因此他為割點。
如果u為根節點,只需要判斷他有幾個連邊就行。