最大強連通圖定義
在有向圖G中,如果兩個頂點間至少存在一條路徑,稱兩個頂點強連通(strongly connected)。如果有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。非強連通圖有向圖的極大強連通子圖,稱爲強連通分量(strongly connected components)。
樸素算法
根據定義我們不難想到, 對同一張圖同時進行正反兩次遍歷, 對兩次的遍歷結果取交集, 這裏得到的便是強連通圖。 在強連通圖中尋找到度數最大的圖即時最大強連通圖。 同時的正反兩次遍歷我們不難發現他的時間複雜度達到了O(N ^ 2 + M)
tarjan算法
算法思想:
用dfs遍歷G中的每個頂點,通dfn[i]表示dfs時達到頂點i的時間,low[i]表示i所能直接或間接達到時間最小的頂點。(實際操作中low[i]不一定最小,但不會影響程序的最終結果)
程序開始時,time初始化爲0,在dfs遍歷到v時,low[v]=dfn[v]=time++,
v入棧(這裏的棧不是dfs的遞歸時系統弄出來的棧)掃描一遍v所能直接達到的頂點k,如果 k沒有被訪問過那麼先dfs遍歷k,low[v]=min(low[v],low[k]);如果k在棧裏,那麼low[v]=min(low[v],dfn[k])(就是這裏使得low[v]不一定最小,但不會影響到這裏的low[v]會小於dfn[v])。掃描完所有的k以後,如果low[v]=dfn[v]時,棧裏v以及v以上的頂點全部出棧,且剛剛出棧的就是一個極大強連通分量。
大致證明過程
1.tarjan算法的基於定理:在任何深度優先搜索中,同一強連通分量內的所有頂點均在同一棵深度優先搜索樹中。也就是說,強連通分量一定是有向圖的某個深搜樹子樹。
2.dfs遍歷時,如果已經遍歷完v所能直接到達的頂點而low[v]=dfn[v],我們知道v一定能到達棧裏v上面的頂點,這些頂點的low一定小於 自己的dfn,不然就會出棧了,也不會小於dfn[v],不然low [v]一定小於dfn[v],所以棧裏v以其v以上的頂點組成的子圖是一個強連通分量,如果它不是極大強連通分量的話low[v]也一定小於dfn[v](這裏不再詳細說),所以棧裏v以其v以上的頂點組成的子圖是一個極大強連通分量。
3.因爲dfn保存的是深搜樹的節點編號, 所以棧中下方的點一定可以到達上方, 而low保存的是這個節點向後(棧的下方)指的最大距離, 也就是向後反向邊的最大距離。 我們說到棧中的節點正向可達, 擁有着一條反向邊之後, 這個棧中的這個區間的元素就成了一個環, 變成了任意兩點可達。 也就是我們所說的強連通, 又因爲tarjan算法會遍歷所有的點, 所以這裏的強連通經過不斷取最大之後, 得到的就是最大強連通分量。
時間複雜度
在這裏每個點進一次棧, 每條邊被遍歷一次。 所以總的確定執行次數時n + m。 即O(N + M).
原始模板
void tarjan(int i)
{
int j;
DFN[i]=LOW[i]=++Dindex;
instack[i]=true;
Stap[++Stop]=i;
for (edge *e=V[i];e;e=e->next)
{
j=e->t;
if (!DFN[j])
{
tarjan(j);
if (LOW[j]<LOW[i])
LOW[i]=LOW[j];
}
else if (instack[j] && DFN[j]<LOW[i])
LOW[i]=DFN[j];
}
if (DFN[i]==LOW[i])
{
Bcnt++;
do
{
j=Stap[Stop--];
instack[j]=false;
Belong[j]=Bcnt;
}
while (j!=i);
}
}
void solve()
{
int i;
Stop=Bcnt=Dindex=0;
memset(DFN,0,sizeof(DFN));
for (i=1;i<=N;i++)
if (!DFN[i])
tarjan(i);
}
動畫演示
一下內容轉自:https://www.byvoid.com/blog/scc-tarjan/
原始有向連通圖:
從節點1開始DFS,把遍歷到的節點加入棧中。搜索到節點u=6時,DFN[6]=LOW[6],找到了一個強連通分量。退棧到u=v爲止,{6}爲一個強連通分量。
返回節點5,發現DFN[5]=LOW[5],退棧後{5}爲一個強連通分量。
返回節點3,繼續搜索到節點4,把4加入堆棧。發現節點4向節點1有後向邊,節點1還在棧中,所以LOW[4]=1。節點6已經出棧,(4,6)是橫叉邊,返回3,(3,4)爲樹枝邊,所以LOW[3]=LOW[4]=1。
繼續回到節點1,最後訪問節點2。訪問邊(2,4),4還在棧中,所以LOW[2]=DFN[4]=5。返回1後,發現DFN[1]=LOW[1],把棧中節點全部取出,組成一個連通分量{1,3,4,2}。
至此,算法結束。經過該算法,求出了圖中全部的三個強連通分量{1,3,4,2},{5},{6}。