強連通分量(Tarjan Kosaraju)

2021-07-31 21:49:35 字數 3483 閱讀 7231

先介紹一下強連通分量:

首先,強連通分量只可能存在於有向圖中,無向圖中是不存在強連通分量的,當然,無向圖中也有對應物,被稱為連通分量,求解無向圖中的連通分量,根據具體要求,可以選擇使用並查集或者dfs。

對於乙個強連通分量中的任意一對頂點(u,v),都能夠保證分量中存在路徑使得u->v,v->u

看圖:

以上用虛線圍繞的部分就是乙個強連通分量,因此上圖中總共含有三個。上圖中由a,b,e這三個頂點構成的分量中,任意兩個頂點間都存在路徑可達。

順便也介紹一下有關「縮點」的概念:

由於強連通分量的特殊性,在一些實際應用中,會將每個強連通分量看成乙個點,然後進行處理。這樣做主要是為了降低圖的複雜度,特別是在強連通分量規模大、數量多的情況中,利用「縮點」能大幅度降低圖的複雜度。

縮點後得到的圖,必定是dag。用反正能夠很方便的進行證明:因為若圖中含有環路,即意味著至少有兩個點彼此可達,那麼按照強連通分量的定義,這兩個點應該屬於乙個分量中,因而在縮點發生後,會被乙個點所代表。由此推導出矛盾。比如,對上圖進行縮點處理,最後的結果就是:

設(a,b,c) -> a』,(f,g) -> b』,(c,d,h) -> c』

因此最後的圖就可以表示為:

接下來,切入正文:

kosaraju演算法比tarjan時間複雜度要高,應用範圍小,還有著爆棧超記憶體的風險,但這個演算法比tarjan好理解很多,雖然很玄學。當然和tarjan一樣,kosaraju也只能用於有向圖中。

kosaraju是基於深度優先搜尋的演算法。這個演算法牽扯到兩個概念,發現時間st,完成時間et。發現時間是指乙個節點第一次被遍歷到時的次序號,完成時間是指某一結點最後一次被遍歷到的次序號。

在加邊時把有向圖正向建造完畢後再反向加邊建一張逆圖。

先對正圖進行一遍dfs,遇到沒訪問過的點就讓其發現時間等於目前的dfs次序號。在回溯時若發現某一結點的子樹全部被遍歷完,就讓其完成時間等於目前dfs次序號。正圖遍歷完後將節點按完成時間入棧,保證棧頂是完成時間最大的節點,棧底是完成時間最小的節點

(玄學內容開始)然後從棧頂開始向下每乙個沒有被反向遍歷過的節點為起點對逆圖進行一遍dfs,將訪問到的點記錄下來(或染色)並彈棧,每一遍反向dfs遍歷到的點就構成乙個強連通分量。

but , 真的很玄學嗎?

其實第一次dfs的操作,非常像乙個基礎的演算法——拓撲排序,只是這裡的目的不是得到每個結點的拓撲有序序列,而是強連通分量的拓補有序序列。

看圖:

原圖是有12個結點的有向有環圖,其中四組都是帶有環路的強連通分量,把這些強連通分量看成乙個點——也就是剛剛提到的縮點。我們可以發現,第一遍dfs得到的是縮點後,圖的拓補排序。這樣,第二遍dfs的呼叫開始點的順序,一定是按照縮點圖的拓撲序列進行的。以上圖為例,第二遍肯定是先在縮點1中的某個點開始,而由於第二遍遍歷的是原圖的逆圖,於是這次dfs只可能訪問強連通分量中的點而且每個都會訪問到,而不會通過強連通分量之外的路徑訪問其他分量,因為路徑的方向都變了。

**:

#include 

using

namespace

std;

const

int maxv = 1024;

int g[maxv][maxv], dfn[maxv], num[maxv], n, m, scc, cnt;

void dfs(int k)

void ndfs(int k)

void kosaraju()

cout

<

kosaraju();

return

0;}

tarjan演算法的基礎還是dfs。我們準備兩個陣列low和dfn。low陣列是乙個標記陣列,記錄該點所在的強連通子圖所在搜尋子樹的根節點的dfn值dfn陣列記錄搜尋到該點的時間,也就是第幾個搜尋這個點的。根據以下幾條規則,經過搜尋遍歷該圖(無需回溯)和對棧的操作,我們就可以得到該有向圖的強連通分量。

1、陣列的初始化:當首次搜尋到點p時,dfn與low陣列的值都為到該點的時間。

2、堆疊:每搜尋到乙個點,將它壓入棧頂。

3、當點p與點p』相連時,如果此時(時間為dfn[p]時)p』不在棧中,p的low值為兩點的low值中較小的乙個。

4、當點p有與點p』相連時,如果此時(時間為dfn[p]時)p』在棧中,p的low值為p的low值和p』的dfn值中較小的乙個。tarjan演算法的操作原理如下:

1、tarjan演算法基於定理:在任何深度優先搜尋中,同一強連通分量內的所有頂點均在同一棵深度優先搜尋樹中。也就是說,強連通分量一定是有向圖的某個深搜樹子樹。

2、可以證明,當乙個點既是強連通子圖ⅰ中的點,又是強連通子圖ⅱ中的點,則它是強連通子圖ⅰ∪ⅱ中的點。

3、這樣,我們用low值記錄該點所在強連通子圖對應的搜尋子樹的根節點的dfn值。注意,該子樹中的元素在棧中一定是相鄰的,且根節點在棧中一定位於所有子樹元素的最下方。

4、強連通分量是由若干個環組成的。所以,當有環形成時(也就是搜尋的下乙個點已在棧中),我們將這一條路徑的low值統一,即這條路徑上的點屬於同乙個強連通分量。

5、如果遍歷完整個搜尋樹後某個點的dfn值等於low值,則它是該搜尋子樹的根。這時,它以上(包括它自己)一直到棧頂的所有元素組成乙個強連通分量。

**:

int top;//這個是用作棧頂的指標  

int stack[max];//維護的乙個棧

bool instack[max];//instack[i]為真表示i在棧中

int dfn[max],low[max];

int belong[max];//belong[i] = a; 表示i這個點屬於第a個連通分量

int bcnt,dindex;//bcnt用來記錄連通分量的個數,dindex表示到達某個點的時間

void tarjan(int u)

else

if (instack[v] && dfn[v] < low[u])

low[u] = dfn[v];

} if (dfn[u] == low[u])//這裡表示找完乙個強連通啦

while (u != v);//一直到v=u都是屬於第bcnt個強連通分量

} } void solve()

至此,介紹結束。(著實忙活了半天,感謝hxm的鼎力相助)

強連通分量 tarjan求強連通分量

雙dfs方法就是正dfs掃一遍,然後將邊反向dfs掃一遍。挑戰程式設計 上有說明。雙dfs 1 include 2 include 3 include 4 include 5 6using namespace std 7const int maxn 1e4 5 8 vector g maxn 圖的鄰...

強連通分量

對於有向圖的乙個頂點集,如果從這個頂點集的任何一點出發都可以到達該頂點集的其餘各個頂點,那麼該頂點集稱為該有向圖的乙個強連通分量。有向連通圖的全部頂點組成乙個強連通分量。我們可以利用tarjan演算法求強連通分量。define n 1000 struct edge e 100000 int ec,p...

強連通分量

在有向圖g中,如果兩個頂點間至少存在一條路徑,稱兩個頂點強連通 strongly connected 如果有向圖g的每兩個頂點都強連通,稱g是乙個強連通圖。非強連通圖有向圖的極大強連通子圖,稱為強連通分量 strongly connected components 下圖中,子圖為乙個強連通分量,因為...