塔揚強連通分量演算法(Tarjan's SCC algorithm)
/ TAR-jun /
塔揚演算法在單一趟深度優先搜尋中找出有向圖所有的強連通分量——不必反向、不必第二趟。它是科薩拉朱法更省的兄弟,其核心訣竅是一個巧妙的記帳數字——low-link——讓每個頂點在這唯一一趟走訪中判斷自己是不是某個 SCC 的「頂端」。
跑一趟 DFS,按頂點被發現的順序給每個頂點一個索引,並維護一個堆疊,存放已見到但尚未歸入某分量的頂點。對每個頂點 u 維護 low(u):從 u 出發、沿樹邊再加上至多一條通往仍在堆疊上頂點的回邊/橫向邊,所能抵達的最小發現索引。你在從遞迴返回時計算它:low(u) 起初是 u 自己的索引,並對每個樹子節點 v 下調為 low(v)、對任何已在堆疊上的鄰居 w 下調為 w 的索引。關鍵時刻:探索完 u 所有的邊後,若 low(u) 仍等於 u 自己的索引,則 u 無法抵達任何更早被發現且仍待處理的頂點——所以 u 是某個 SCC 的根。你接著把堆疊彈到並包含 u 為止,那些被彈出的頂點恰好構成一個強連通分量。直覺是:low(u) = index(u) 意味沒有任何回路能從以 u 為根的子樹向上逃進尚未完成的部分,這正是 u 領頭一個極大相互可達團塊的條件。
塔揚以單一趟走訪跑在 O(n + m),只用 O(n) 的額外空間存索引、low-link 與堆疊——在趟數上嚴格比科薩拉朱精簡,雖然兩者共享相同的漸進成本。在看重單趟的競賽程式設計與正式程式碼中,它是首選。同樣的 low-link 想法用於無向圖,還能產出關節點與橋,所以在這裡學會它會三倍受益。誠實的提醒:low-link 記帳繁瑣、容易出現微妙的錯誤(哪些鄰居更新 low、在堆疊的檢查、彈出的條件),所以它獎勵細心的實作;若你要的是正確與清晰而非寫程式的速度,科薩拉朱的兩趟 DFS 較容易寫對。
圖 a->b、b->c、c->a、c->d。DFS 索引 a=0、b=1、c=2、d=3。從 c 出發的回邊 c->a(a 在堆疊上)把 low(c) 拉到 0;這往上傳播使 low(c)=low(b)=low(a)=0。頂點 d 的 low(d)=3=index(d),所以 d 單獨彈出:SCC {d}。回到 a,low(a)=0=index(a):彈出 c、b、a 作為 SCC {a,b,c}。
當 low(u) 等於 u 自己的索引時,沒有逃逸路徑能抵達更早的待處理頂點,所以 u 是某 SCC 的根,堆疊把它彈出。
low(u) 必須只追蹤透過「仍在堆疊上」的頂點可達的最小索引(略過已歸入完成 SCC 的鄰居)。漏掉在堆疊的檢查,是把本該分開的分量合併的經典錯誤。