深度優先搜索(DFS)算法

正如算法名稱那樣,深度優先搜索所遵循的搜索策略是儘可能“深”地搜索圖。在深度優先搜索中,對於最新發現的頂點,如果它還有以此爲起點而未探測到的邊,就沿此邊繼續漢下去。當結點v的所有邊都己被探尋過,搜索將回溯到發現結點v有那條邊的始結點。這一過程一直進行到已發現從源結點可達的所有結點爲止。如果還存在未被發現的結點,則選擇其中一個作爲源結點並重復以上過程,整個進程反覆進行直到所有結點都被發現爲止。

和廣度優先搜索類似,每當掃描已發現結點u的鄰接表從而發現新結點v時,深度優先搜索將置v的先輩域π[v]爲u。和寬度優先搜索不同的是,前者的先輩子圖形成一棵樹,而後者產生的先輩子圖可以由幾棵樹組成,因爲搜索可能由多個源頂點開始重複進行。因此深度優先搜索的先輩子圖的定義也和寬度優先搜索稍有不同:

Gπ=(V,Eπ),Eπ={(π[v],v)∈E:v∈V∧π[v]≠NIL}

深度優先搜索的先輩子圖形成一個由數個深度優先樹組成的深度優先森林。Eπ中的邊稱爲樹枝。

和寬度優先搜索類似,深度優先在搜索過程中也爲結點着色以表示結點的狀態。每個頂點開始均爲白色,搜索中被發現時置爲灰色,結束時又被置成黑色(即當其鄰接表被完全檢索之後)。這一技巧可以保證每一頂點搜索結束時只存在於一棵深度優先樹上,因此這些樹都是分離的。

除了創建一個深度優先森林外,深度優先搜索同時爲每個結點加蓋時間戳。每個結點v有兩個時間戳:當結點v第一次被發現(並置成灰色)時記錄下第一個時間戳d[v],當結束檢查v的鄰接表時(並置v爲黑色)記錄下第二個時間截f[v]。許多圖的算法中都用到時間戳,他們對推算深度優先搜索進行情況是很有幫助的。

下列過程DFS記錄了何時在變量d[u]中發現結點u以及何時在變量f[u]中完成對結點u的檢索。這些時間戳爲1到2|V|之間的整數,因爲對每一個v中結點都對應一個發現事件和一個完成事件。對每一頂點u,有

d[u]<f[u]         (1)

在時刻d[u]前結點u爲白色,在時刻d[u]和f[u]之間爲灰色,以後就變爲黑色。

下面的僞代碼就是一個基本的深度優先搜索算法,輸人圖G可以是有向圖或無向圖,變量time是一個全局變量,用於記錄時間戳。

  procedure DFS(G);
   begin
1    for 每個頂點u∈V[G] do
       begin
2        color[u]←White;
3        π[u]←NIL;
       end;
4    time←0;
5    for 每個頂點u∈V[G] do
6      if color[u]=White
7          then DFS_Visit(G,u);
end;

 procedure DFS_Visit(G,u);
  begin
1   color[u]←Gray;              Δ白色結點u已被發現
2   d[u]←time←time+1;
3   for 每個頂點v∈Adj[u] do     Δ探尋邊(u,v)
4      if color[v]=White 
          then begin
5                π[v]←u;
6                DFS_Visit(G,v);
               end;
7   color[u]←Black;             Δ完成後置u爲黑色
8   f[u]←time←time+1;
end;

圖2說明了DFS在圖1所示的圖上執行的過程。被算法探尋到的邊要麼爲陰影覆蓋 (如果該邊爲樹枝),要麼成虛線形式 (其他情況)。對於非樹枝的邊,分別標明B(或F)以表示反向邊、交叉邊或無向邊。我們用發現時刻Z完成時刻的形式對結點加蓋時間戳。

圖1 一個有向圖圖

圖2 深度優先搜索算法DFS在有向圖圖1上的執行過程

過程DFS執行如下。第1-3行把所有結點置爲白色,所有π域初始化爲NIL。第4行復位全局變量time,第5-7行依次檢索V中的結點,發現白色結點時,調用DFS_Visit去訪問該結點。每次通過第7行調用DFS_Visit時,結點u就成爲深度優先森林中一棵新樹的根,當DFS返回時,每個結點u都對應於一個發現時刻d[u]和一個完成時刻f[u]。

每次開始調用DFS_Visit(u)時結點u爲白色,第1行置u爲灰色,第2行使全局時間變量增值並存於d[u]中,從而記錄下發現時刻d[u],第3-6行檢查和u相鄰接的每個頂點v,且若v爲白色結點,則遞歸訪問結點v。在第3行語句中考慮到每一個結點v∈Adj[u]時,我們可以說邊(u,v)被深度優先搜索探尋。最後當以u爲起點的所有邊都被探尋後,第7-8行語句置u爲黑色並記錄下完成時間f[u]。

算法DFS運行時間的複雜性如何?DFS中第1-2行和5-7行的循環佔用時間爲O(V),這不包括執行調用DFS_Visit過程語句所耗費的時間。事實上對每個頂點v∈V,過程DFS_Visit僅被調用一次,因爲DFS_Visit僅適用於白色結點且過程首先進行的就是置結點爲灰色,在DFS_Visit(v)執行過程中,第3-6行的循環要執行|Adj[v]|次。因爲∑v∈V|Adj[v]| =θ(E),因此執行過程DFS_Visit中第2-5行語句佔用的整個時間應爲θ(E)。所以DFS的運行時間爲θ(V+E)。

深度優先搜索的性質

依據深度優先搜索可以獲得有關圖的結構的大量信息。也許深度優先搜索的最基本的特徵是它的先輩子圖G,形成一個由樹組成的森林,這是因爲深度優先樹的結構準確反映了DFS_Visit中遞歸調用的結構的緣故,即u=π[v]當且僅當在搜索u的鄰接表過程中調用了過程DFS_Visit(v)。

圖3 深度優先搜索的性質

深度優先搜索的另一重要特性是發現和完成時間具有括號結構,如果我們把發現頂點u用左括號“(u”表示,完成用右括號“u)”表示,那麼發現與完成的記載在括號被正確套用的前提下就是一個完善的表達式。例如,圖3顯示了深度優先搜索的性質。(a)對一個有向圖進行深度優先搜索的結果。結點的時間戳與邊的類型的表示方式與圖2相同。(b)圖中的括號表示對應於每個結點的發現時刻和完成時刻的組成的區間。每個矩形跨越相應結點的發現時刻與完成時刻所設定的區間。圖中還顯示了樹枝。如果兩個區間有重疊,則必有一個區間嵌套於另一個區間內,且對應於較小區間的結點是對應於較大區間的結點的後裔。(c)對(a)中圖的重新描述,使深度優先樹中所有樹枝和正向邊自上而下,而所有反向邊自下而上從後裔指向祖先。下面的定理給出了標記括號結構的另外一種辦法。

定理1 括號定理

在對有向圖或無向圖G=(V,E)的任何深度優先搜索中,對於圖中任意兩結點u和v,下述三個條件中有且僅有一條成立:

  • 區間[d[u],f[u]]和區間[d[v],f[v]]是完全分離的;
  • 區間[d[u],f[u]]完全包含於區間[d[v],f[v]]中且在深度優先樹中u是v的後裔;
  • 區間[d[v],f[v]]完全包含於區間[d[u],f[u]]中且在深度優先樹中v是u的後裔。

證明:

先討論d[u]<d[v]的情形,根據d[v]是否小於f[u]又可分爲兩種情況:第一種情況,若d[v]<f[u],這樣v已被發現時u結點依然是灰色,這就說明v是u的後裔,再者,因爲結點v比u發現得較晚,所以在搜索返回結點u並完成之前,所有從v出發的邊都己被探尋並已完成,所以在這種條件下區間[d[v],f[v]]必然完全包含於區間[d[u],f[u]]。第二種情況,若f[u]<d[v],則根據不等式(1),區間[d[u],f[u]]和區間[d[v],f[v]]必然是分離的。

對於d[v]<d[u]的情形類似可證,只要把上述證明中u和v對調一下即可。(證畢)

推論1 後裔區間的嵌入

在有向或無向圖G的深度優先森林中,結節v是結點u的後裔當且僅當d[u]<d[v]<f[v]<f[u]。

證明:直接由定理1推得。(證畢)

在深度優先森林中若某結點是另一結點的後裔,則下述定理將指出它的另一重要特徵。

定理2 白色路徑定理

在一個有向或無向圖G=(V,E)的深度優先森林中,結點v是結點u的後裔當且僅當在搜索發現u的時刻d[u],從結點u出發經一條僅由白色結點組成的路徑可達v。

證明:

→:假設v是u的後裔,w是深度優先樹中u和v之間的通路上的任意結點,則w必然是u的後裔,由推論1可知d[u]<d[w],因此在時刻d[u],w應爲白色。

←:設在時刻d[u],從u到v有一條僅由白色結點組成的通路,但在深度優先樹中v還沒有成爲u的後裔。不失一般性,我們假定該通路上的其他頂點都是u的後裔(否則可設v是該通路中最接近u的結點,且不爲u的後裔),設w爲該通路上v的祖先,使w是u的後裔(實際上w和u可以是同一個結點)。根據推論1得f[w]≤f[u],因爲v∈Adj[w],對DFS_Visit(w)的調用保證完成w之前先完成v,因此f[v]<f[w]≤f[u]。因爲在時刻d[u]結點v爲白色,所以有d[u]<d[v]。由推論1可知在深度優先樹中v必然是u的後裔。(證畢)

邊的分類

在深度優先搜索中,另一個令人感興趣的特點就是可以通過搜索對輸入圖G=(V,E)的邊進行歸類,這種歸類可以發現圖的很多重要信息。例如一個有向圖是無迴路的,當且僅當深度優先搜索中沒有發現“反向邊”。

根據在圖G上進行深度優先搜索所產生的深度優先森林G,我們可以把圖的邊分爲四種類型。

  1. 樹枝,是深度優先森林Gπ中的邊,如果結點v是在探尋邊(u,v)時第一次被發現,那麼邊(u,v)就是一個樹枝。
  2. 反向邊,是深度優先樹中連結結點u到它的祖先v的那些邊,環也被認爲是反向邊。
  3. 正向邊,是指深度優先樹中連接頂點u到它的後裔的非樹枝的邊。
  4. 交叉邊,是指所有其他類型的邊,它們可以連結同一棵深度優先樹中的兩個結點,只要一結點不是另一結點的祖先,也可以連結分屬兩棵深度優先樹的結點。

在圖2和圖3中,都對邊進行了類型標示。圖3(c)還說明了如何重新繪製圖3(a)中的圖,以使深度優先樹中的樹枝和正向邊向下繪製,使反向邊向上繪製。任何圖都可用這種方式重新繪製。
可以對算法DFS進行一些修改,使之遇到邊時能對其進行分類。算法的核心思想在於可以根據第一次被探尋的邊所到達的結點v的顏色來對該邊(u,v)進行分類(但正向邊和交叉邊不能用顏色區分出)。

  1. 白色表明它是樹枝。
  2. 灰色說明它是反向邊。
  3. 黑色說明它是正向邊或交叉邊。

第一種情形由算法即可推知。在第二種情形下,我們可以發現灰色結點總是形成一條對應於活動的DFS_Visit調用堆棧的後裔線性鏈,灰色結點的數目等於最近發現的結點在深度優先森林中的深度加1,探尋總是從深度最深的灰色結點開始,因此達到另一個灰色結點的邊所達到的必是它的祖先。餘下的可能就是第三種情形,如果d[u]<d[v],則邊(u,v)就是正向邊,若d[u]>d[v],則(u,v)便是交叉邊。

因此可以得到下面結論:對於某條邊(u,v),

  • 當且僅當d[u]<d[v]<f[v]<f[u]時,是樹枝邊或正向邊;
  • 當且僅當d[v]<d[u]<f[u]<f[v]時,是反向邊;
  • 當且僅當d[v]<f[v]<d[u]<f[u]時,是交叉邊;

該結論的證明略。

在無向圖中,由於(u,v)和(v,u)實際上是同一條邊,所以對邊進行這種歸類可能產生歧義。在這種情況下,圖的邊都被歸爲歸類表中的第一類,對應地,我們將根據算法執行過程中首先遇到的邊是(u,v)還是(v,u)來對其進行歸類。

下面我們來說明在深度優先搜索無向圖時不會出現正向邊和交叉邊。

定理3

在對無向圖G進行深度優先搜索的過程中,G的每條邊要麼是樹是反向邊。

證明:

設(u,v)爲G的任意一邊,不失一般性,假定d[u]<d[v]。則因爲v在u的鄰接表中,所以我們必定在完成u之前就已發現並完成v。如果邊(u,v)第一次是按從u到v的方向被探尋到,那麼(u,v)必是一樹枝。如果(u,v)第一次是按從v到u的方向被探尋到,則由於該邊被第一次探尋時u依然是灰色結點,所以(u,v)是一條反向邊。(證畢)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章