DFS 對個人來講真是個奇怪的遍歷算法,但最後再仔細看來卻是相當重要的算法,難怪在CLRS的chapter notes中講到由Hopcroft和Tarjan首先意識到DFS的重要性,記住Tarjan這個人,他在圖論上有着相當大的貢獻,而且首次引入了amortized analysis(分攤分析),這兩位同時獲得了圖靈獎。DFS如此重要,以下幾乎全翻譯自CLRS,包括部分習題的解答。
顧名思義,DFS就是深度優先搜索,先儘可能的找一條包含足夠多點的簡單路徑,如果找不到再向上一層結點回溯,DFS過程可以使用遞歸函數方便的實現。DFS中對於每一個點u,有一個發現時間d(u)和結束時間f(u),可以認爲點u的生存週期爲(d(u), f(u))。
定理1:圖G=(V,E)中任意兩個點u、v的生存週期均不相交,即只有相離、包含兩種關係。這稱爲DFS的括號匹配。
證明:不失一般性,假設 d(u) <d(v),分兩種情況:
(1) d(v) <f(u),發現點v時點u爲灰色,由DFS過程可知v爲u的子孫結點,如此便有d(u) < d(v) < f(v) < f(u),此時兩點爲包含關係。
(2) d(v) >f(u),則顯然有 d(u) < f(u) < d(v) < f(v),此時兩點爲相離關係
由此定理有DFS 中v爲u的子孫結點 當且僅當 d(u) < d(v)< f(v) < f(u)
定理2:DFS 中v爲u的子孫結點 當且僅當訪問到u時存在一條從u到v的白色路徑,故此定理又稱爲白色路徑定理。
這個定理如此重要,後續很多結論都需要這個條件。證明:
(1) 訪問u時,u到v的路徑上全是白色結點
假設此時v不是u的子孫結點,找u到v路徑上第一個不是u子孫結點的結點,設爲x,其父結點設爲w(w可能和u相同,也可能是u的子孫結點),於是有f(u)>= f(w),由於點x假設不是u的子孫結點,故d(x)>f(u),但注意點x不可能在f(w)時刻還沒有被發現,因爲w=>x有一條路徑,於是有d(x) < f(w),即d(x) > f(u) >=f(w) >d(x),矛盾,故假設不成立,即在u到v的路徑上全是u的子孫結點。
(2) DFS過程後,v爲u的子孫結點,根據括號匹配定理,u到v的路徑上的任意結點x,都有d(u) < d(x) < f(x) < f(u),故當發現結點u時結點x必定爲白色結點。
定義:DFS邊的分類
tree: DFS樹的邊
back: DFS樹中從某結點到其祖先結點的邊
forward: DFS樹中從某結點到其子孫結點的邊
cross: 其他結點,可以從一棵DFS樹到另一棵DFS樹的邊,也可以同一個DFS樹內沒有沒有祖先關係的邊
剛開始覺得邊的分類極其無聊,後續在做題過程才發現有實際意義。
定理3,DFS無向圖,只有tree、back邊
證明,任意邊u<=>v,不失一般性,假設d(u)< d(v),根據白色路徑定理v爲u的子孫結點,於是不可能爲cross邊。如果第一次訪問邊是從u發起時,則v必定爲白色,於是邊爲tree邊;如果第一次訪問邊是從v發起時,則爲back邊
22.3-7 舉出一個反例。有向圖,當u到v有一條路徑,而且d(u)< d(v),則v爲u的子孫結點。
u <==> x ==>v
如上,從x開始DFS,先選擇u,再選擇v,則有d(u) <d(v),但是v不是u的子孫結點