Tarjan算法的操作原理如下:
- 在任何深度優先搜索中,同一強連通分量內的所有頂點均在同一棵深度優先搜索樹中。也就是說,強連通分量一定是有向圖的某個深度搜樹子樹。
- 我們用low值記錄該點所在強連通子圖對應的搜索子樹的根節點的Dfn值。注意,該子樹中的元素在棧中一定是相鄰的,且根節點在棧中一定位於所有子樹元素的最下方。
- 強連通分量是由若干個環組成的。所以,當有環形成時(也就是搜索的下一個點已在棧中),我們將這一條路徑的low值統一,即這條路徑上的點屬於同一個強連通分量。
- 如果遍歷完整個搜索樹後某個點的dfn值等於low值,則它是該搜索子樹的根。這時,它以上(包括它自己)一直到棧頂的所有元素組成一個強連通分量。
low值和Dfn值各代表什麼:
Dfn值代表在一次深度優先搜索中各節點所處的次序,如果一個圖是強連通的,它必然是可以被一個環串起所有的點,反之則不然,所以葉子節點必然是存在的。
什麼樣的節點可以看成根節點:low[v]=Dfn[v]。這個根節點是強連通子圖的根節點。
== 僞代碼 ==
'''algorithm''' tarjan '''is'''
'''input:''' 圖 ''G'' = (''V'', ''E'')
'''output:''' 以所在的強連通分量劃分的頂點集
''index'' := 0
''S'' := empty ''// 置棧爲空''
'''for each''' ''v'' '''in''' ''V'' '''do'''
'''if''' (''v''.index is undefined)
strongconnect(''v'')
'''end if'''
'''function''' strongconnect(''v'')
''// 將未使用的最小index值作爲結點v的index''
''v''.index := ''index''
''v''.lowlink := ''index''
''index'' := ''index'' + 1
''S''.push(''v'')
''// 考慮v的後繼結點''
'''for each''' (''v'', ''w'') '''in''' ''E'' '''do'''
'''if''' (''w''.index is undefined) '''then'''
''// 後繼結點w未訪問,遞歸調用''
strongconnect(''w'')
''v''.lowlink := min(''v''.lowlink, ''w''.lowlink)
'''else if''' (''w'' is in ''S'') '''then'''
''// w已在棧S中,亦即在當前強連通分量中''
''v''.lowlink := min(''v''.lowlink, ''w''.index)
'''end if'''
''// 若v是根則出棧,並求得一個強連通分量''
'''if''' (''v''.lowlink = ''v''.index) '''then'''
start a new strongly connected component
'''repeat'''
''w'' := ''S''.pop()
add ''w'' to current strongly connected component
'''until''' (''w'' = ''v'')
output the current strongly connected component
'''end if'''
'''end function'''
變量<tt>index</tt>是深度優先搜索的結點計數器。<tt>S</tt>是棧,初始爲空,用於存儲已經訪問但未被判定屬於任一強連通分量的結點。注意這並非一個一般深度優先搜索的棧,結點不是在以它爲根的子樹搜索完成後出棧,而是在整個強連通分量被找到時。
最外層循環用於查找未訪問的結點,以保證所有結點最終都會被訪問。<tt>strongconnect</tt>進行一次深度優先搜索,並找到結點<tt>v</tt>的後繼結點構成的子圖中所有的強連通分量。
當一個結點完成遞歸時,若它的<tt>lowlink</tt>仍等於<tt>index</tt>,那麼它就是強連通分量的根。算法將在此結點之後入棧(包含此結點)且仍在棧中的結點出棧,並作爲一個強連通分量輸出。