Tarjan 演算法求強連通分量

2022-09-14 12:18:08 字數 3526 閱讀 5661

回到隊裡2周,開始系統複習舊演算法和學習新演算法,整理之前沒整理過的演算法

強連通圖(strongly connect graph)是指:如果在有向圖 \(g\) 中,對於每兩個不同的點 \(v_i,v_j\),有一條 \(v_i\) 到 \(v_j\) 的簡單路徑存在,那麼稱 \(g\) 是強連通圖。

有向圖的極大強連通子圖稱為有向圖的強連通分量(strongly connected component,簡稱scc)。

如圖,圖中點1、2、3構成了乙個強連通分量。

tarjan演算法(以發現者robert tarjan命名)是乙個在圖中查詢強連通分量的演算法。

此演算法以乙個有向圖作為輸入,並按照所在的強連通分量給出其頂點集的乙個劃分。圖中的每個節點只在乙個強連通分量**現,即使是在有些節點單獨構成乙個強連通分量的情況下(比如圖**現了樹形結構或孤立節點)。

演算法的基本思想如下:任選一節點開始進行深度優先搜尋(若深度優先搜尋結束後仍有未訪問的節點,則再從中任選一點再次進行)。搜尋過程中已訪問的節點不再訪問。搜尋樹的若干子樹構成了圖的強連通分量。

節點按照被訪問的順序存入堆疊中。從搜尋樹的子樹返回至乙個節點時,檢查該節點是否是某一強連通分量的根節點並將其從堆疊中刪除。如果某節點是強連通分量的根,則在它之前出堆疊且還不屬於其他強連通分量的節點構成了該節點所在的強連通分量。

以上是tarjan演算法比較高階的定義,現在我們來簡易的理解一下它的原理。

tarjan演算法的核心是維護兩個重要的陣列:dfnlow

dfn表示的是「時間戳」,dfn[i]代表了節點 \(i\) 被遍歷到的次序,注意dfn的值不可以改變。

low則表示的是當前子樹中,仍在堆疊裡的最小時間戳。容易知道,只要兩個節點擁有相等的low值,那麼它們一定在同乙個子樹裡,同時也就在同乙個 scc 中。

在每次搜尋的時候,要初始化dfn[x]=low[x]=++total(這裡 total 初始值為 0),因為每乙個節點都可以看作乙個 scc ,所以這裡暫時把low[x]賦值為時間戳dfn[x]

我們從當前節點出發,搜尋這個節點延伸出的子樹。在搜尋子樹的過程中,假設遇到乙個點沒有搜尋過,那麼遞迴搜尋它,搜尋之後,用搜尋節點的low值更新當前節點的low值;如果這個點已經被搜尋過了,那麼用搜到點的dfn值更新當前點的low值。

給一張有向圖,如下。

給一張有向圖,如下。

dfn:1,0,2,0,3,4

low:1,0,2,0,3,4

stack:1,3,5,6

此時dfn[6]==low[6],退棧直到棧頂元素為 \(x\) 為止。此時,找到 6 為乙個 scc。

更新:dfn:1,0,2,0,3,4

low:1,0,2,0,3,4

stack:1,3

發現 3 還有子節點 4 ,4 有子節點 1 和 6(6 已經被搜過,不再搜尋),那麼 3 和 4 的low值被 1 的dfn值更新。

更新:dfn:1,0,2,5,3,4

low:1,0,1,1,3,4

stack:1,3,4

更新:dfn:1,6,2,5,3,4

low:1,5,1,1,3,4

stack:1,3,4,2

綜上,這個有向圖的強連通分量有:5、6、(1,2,3,4)。

使用 tarjan 演算法的目的是實現:重構圖。

對於乙個有向有環圖,使用 tarjan 進行 scc 構建(縮點)之後,它就變成了乙個dag。

對於 dag ,有很多方便的性質,比如拓撲dp,你甚至可以在上面跑網路流。

我們結合一道例題來看:洛谷p3387【模板】縮點

在這個問題中,需要我們求出最大點權和,但是顯然不可以直接 dp ,因為一旦乙個點被更新之後,我們用它更新其他點之後,這個點還有可能被再次更新(因為有環的存在)。也就是不滿足我們所說的 dp 需要滿足的「無後效性原則」。

那麼怎麼辦呢?題目中給出:邊可以重複走,但是遇到的點只計算一次點權。想到可以對這個圖進行 tarjan 縮點:只要我們走到其中任意乙個 scc 裡,我們就可以獲得這個 scc 包含的所有點的點權,並且從這個 scc 包含的點中任意一條邊離開這個 scc。何況,在 tarjan 之後,這個有向有環圖變成了乙個 dag,我們可以進行拓撲排序,然後 dp 來解決上面所說的後效性問題。(用拓撲序 dp 可以保證更新乙個點之後,這個點在之後的求解中不再更新,因為該點入度為 0 ,無論如何也不可能再走到這個點)。

#include#include#include#include#include#include//using namespace std;

typedef long long ll;

const int maxm=100005;

const int maxn=10005;

struct point;

struct point scc[maxn];

std::vectorv[maxm];

std::stackstk;

int visit[maxn],dfn[maxn],low[maxn],value_of_each_point[maxn],belong_to_scc[maxn],dp[maxn];

//依次表示:i號點是否訪問過,dfn,low,i號點的點權,i號點屬於哪個scc,dp陣列

int n,m,cnt,tot;

//cnt表示scc個數,tot同上的total

inline int read()

while('0'<=ch&&ch<='9')

return fh*x;

}void tarjan(int x){

dfn[x]=low[x]=++tot;

stk.push(x);

visit[x]=1;

for(int i=0;iqueue;

memset(in,0,sizeof in);

memset(dp,0,sizeof dp);

for(int i=1;i<=cnt;i++)

for(int j=0;jps:感謝sy巨神幫我調**

強連通分量 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 演算法 求強連通分量)

全網最詳細tarjan演算法講解,我不敢說別的。反正其他tarjan演算法講解,我看了半天才看懂。我寫的這個,讀完一遍,發現原來tarjan這麼簡單!tarjan演算法,乙個關於 圖的聯通性的神奇演算法。基於dfs 迪法師 演算法,深度優先搜尋一張有向圖。注意!是有向圖。根據樹,堆疊,打標記等種種神...

Tarjan演算法求強連通分量

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