本文**自:
說到以tarjan命名的演算法,我們經常提到的有3個,其中就包括本文所介紹的求強連通分量的tarjan演算法。而提出此演算法的普林斯頓大學的robert e tarjan教授也是2023年的圖靈獎獲得者(具體原因請看本博「歷屆圖靈獎得主」一文)。
首先明確幾個概念。
強連通圖。在乙個強連通圖中,任意兩個點都通過一定路徑互相連通。比如圖一是乙個強連通圖,而圖二不是。因為沒有一條路使得點4到達點1、2
2.強連通分量。在乙個非強連通圖中極大的強連通子圖就是該圖的強連通分量。比如圖三中子圖是乙個強連通分量,子圖是乙個強連通分量。
其實,tarjan演算法的基礎是dfs。我們準備兩個陣列low和dfn。low陣列是乙個標記陣列,記錄該點所在的強連通子圖所在搜尋子樹的根節點的dfn值(很繞嘴,往下看你就會明白),dfn陣列記錄搜尋到該點的時間,也就是第幾個搜尋這個點的。根據以下幾條規則,經過搜尋遍歷該圖(無需回溯)和對棧的操作,我們就可以得到該有向圖的強連通分量。
陣列的初始化:當首次搜尋到點p時,dfn與low陣列的值都為到該點的時間。
堆疊:每搜尋到乙個點,將它壓入棧頂。
當點p有與點p』相連時,如果此時(時間為dfn[p]時)p』不在棧中,p的low值為兩點的low值中較小的乙個。
當點p有與點p』相連時,如果此時(時間為dfn[p]時)p』在棧中,p的low值為p的low值和p』的dfn值中較小的乙個。
每當搜尋到乙個點經過以上操作後(也就是子樹已經全部遍歷)的low值等於dfn值,則將它以及在它之上的元素彈出棧。這些出棧的元素組成乙個強連通分量。
由於每個頂點只訪問過一次,每條邊也只訪問過一次,我們就可以在o(n+m)的時間內求出有向圖的強連通分量。但是,這麼做的原因是什麼呢?
tarjan演算法的操作原理如下:
tarjan演算法基於定理:在任何深度優先搜尋中,同一強連通分量內的所有頂點均在同一棵深度優先搜尋樹中。也就是說,強連通分量一定是有向圖的某個深搜樹子樹。
可以證明,當乙個點既是強連通子圖ⅰ中的點,又是強連通子圖ⅱ中的點,則它是強連通子圖ⅰ∪ⅱ中的點。
這樣,我們用low值記錄該點所在強連通子圖對應的搜尋子樹的根節點的dfn值。注意,該子樹中的元素在棧中一定是相鄰的,且根節點在棧中一定位於所有子樹元素的最下方。
強連通分量是由若干個環組成的。所以,當有環形成時(也就是搜尋的下乙個點已在棧中),我們將這一條路徑的low值統一,即這條路徑上的點屬於同乙個強連通分量。
如果遍歷完整個搜尋樹後某個點的dfn值等於low值,則它是該搜尋子樹的根。這時,它以上(包括它自己)一直到棧頂的所有元素組成乙個強連通分量。
強連通分量**
1view codeinthead[n], tot;
2struct
node
3edge[n<<1];6
int dfn[n], low[n]; //
深度優先數和最大能訪問的節點
7int instack[n]; //
是否在棧中
8int st[n]; //棧9
intcnt, n, top, ght;
1011
void
init()
1219
20void add_edge(int s, int
t)21
3031
void dfs(int
u)32
45else
if(instack[v]) low[u] =min(low[u], dfn[v]);46}
47//
以下是輸出強連通分量ght的各個節點
48if(low[u]==dfn[u])
4959 printf("
}\n"
);60}61
}6263void
tarjan()
64
tarjan演算法求強連通分支
近期做資料結構課程設計,遇到了有向圖的強連通分支的問題,網上採用的是基於深度優先遍歷的tarjan演算法,在考了許多部落格後,還是有些迷,在思考了一番後,終於明白了,所以寫了這篇文章。首先,明確以下幾點。tarjan演算法基於深度優先遍歷。使用時,會用到兩個棧。乙個棧用於dfs,如果是遞迴的tarj...
強連通分支
1。定義 有向圖強連通分量 在有向圖g中,如果兩個頂點間至少存在一條路徑,稱兩個頂點強連通 strongly connected 如果有向圖g的每兩個頂點都強連通,稱g是乙個強連通圖。非強連通圖有向圖的極大強連通子圖,稱為強連通分量 strongly connected components 現在來...
有向連通分量的Tarjan演算法
演算法偽 描述 tarjan root 1 初始化 def low,map 2 stack root 將 root 節點入棧 3 對 與 root 相連的每個 節點 p 如果 p 在 stack 中 那麼 low root min low root def p 並且 call tarjan p 4 ...