編輯於 2023年10月2日20點45分 關於後向邊和前向邊的一些概念問題的補充說明
深度優先搜尋:總是對最近才發現的節點v的出發邊進行探索,直到該節點的所有出發邊都被發現為止。一旦某個節點v的出發邊都被發現,搜尋則回溯到v的前驅節點。
如果還存在尚未發現的節點,則深度優先搜尋將這些未被發現的節點中任選乙個作為新的源節點。並重複該過程,直到所有節點都被發現。
注意:可以看到,在此處的限定條件是直到所有的節點都被發現為止,這也是深度優先搜尋與廣度優先搜尋不同的地方,由於廣度優先搜尋常常用來尋找從特定源節點出發的最短路徑,而深度優先演算法則常常作為另乙個演算法中的子程式。
正由於深度優先搜尋的這個性質,其形成的前驅子圖也與廣度優先搜尋形成的不同,因為前者可以通過源節點形成多棵樹。
前驅子圖(in dfs):對於圖g =(v, ep),其中ep = 。深度優先搜尋的前驅子圖形成乙個由多棵深度優先樹構成的深度優先森林,森林ep的邊仍然被稱為樹邊。
時間戳:dfs對於每個節點v來說,都有兩個時間戳:第乙個v.d記錄節點v第一次被發現的時間,第二個v.f則記錄完成對v的鄰接鍊錶完成掃瞄的時間。
顯然,對於同乙個節點的d和f來說,有: u.d<u.f。
以下是偽**實現,其中time是實現時間戳的乙個全域性變數。我們應當關心的是其應該在什麼時間增加:
1、每當掃瞄到乙個白色的節點的時候,增加1,並將該白色節點的u.d賦為增加後的time。
2、沒當對某個節點的鄰接鍊錶進行充分的掃瞄後,增加1,並將該灰色節點的u.f賦為增加後的time。
dfs(g)
dfs-visit(g, u) // 這是乙個遞迴函式
//經過以上的兩個函式,可以看到,真正執行對白色節點進行尋找深度搜尋的是dfs-visit函式
以下為c語言實現:
應當注意的是,儲存在每乙個節點的v即節點的內部編號vertex,使得能夠從該節點的指標來尋找到它的鄰接鍊錶。
void dfs(ptrtograph g)
time = 0;
for (int i = 0; i < maxvertexnum; i++) }
void dfs_visit(ptrtograph g, int i)
} g->a[i].color = black;
time++;
g->a[i].f = time;
}
演算法所耗時間分析:
對於每個節點v,都進行了一次dfs-visit操作:因為dfs-visit操作僅在節點為白色時執行,並在第一時間將節點塗為灰色。
之後,再分析dfs_visit中的for迴圈語句:除卻最後的dfs_visit語句,我們對每乙個節點的鄰接鍊錶進行掃瞄,則所需要的時間為θ(|e|),因此,總執行時間為θ(v+e)。
深度優先搜尋的性質:
1、圖結構:dfs形成的樹的結構與dfs-visit的遞迴呼叫結構完全對應。
2、括號化結構(parenthesis structure):若以左括號(u來表示節點u的發現,同時以u)來表示節點u的掃瞄結束,那麼整個dfs中所有節點的發現和完成關於時間的括號化記載將形成乙個規整的表示式,即所有的括號都巢狀在一起。
定理1:(括號化定理)對圖g=(v, e)進行的任意dfs中,對於兩個節點u, v,下列三種情況只有一種成立:
1、區間[u.d, u.f]與區間[v.d, v.f]完全分離,此時u、v互不為對方的後代。
2、區間[u.d, u.f]完全包含在[v.d, v.f]中,則u是v的後代。
3、區間[v.d, v.f]完全包含在[u.d, u.f]中,則v是u的後代。
證明:(ps:其實作者認為這個定理的證明特別簡單:根據演算法中被發現的後代節點的dfs_visit操作在其第一次被父節點發現時,實際處於其父節點的dfs_visit操作中即可得證,不過在此仍然給書上的證明方法。)
回憶:對於某節點v,其屬性d和f滿足:v.d<v.f,根據此,僅分析u.d<v.d的情況,另一種情況與之類似。
在這種情況下,又細分為兩種小情形:
a、當v.du.f,與條件不符。)同時,由於後代v的處理晚於u的處理,因此當對u.f進行賦值的時候,即為搜尋完從u出發的所有邊時,折返回來再將u塗為黑色。那麼,在此時,必然會處理完v節點,因此v.f屬性的賦值先於u.f,因此有:u.db、當u.f<v.d的時候,此時必然為u.d推論2(後代區間的巢狀):在圖g形成的dff(depth first forest)中,節點v是u的真後代當且僅當u.d定理3(白色路徑定理):在圖g形成的dff中,節點v是節點u的後代當且僅當在發現節點u的時間u.d,存在一條從節點u到節點v的全部由白色節點構成的路徑。
證明:前推後:此時已知v是u的後代,推白色路徑。
當v = u時,顯然從u到v的路徑僅包含節點u,此時在發現v前,或者說u,其為白色的;
當v是u的真子代時,由u,d後推前:此時已知白色路徑,推v是u的後代。
利用反證法進行證明:假定存在從u到v的白色路徑時,v卻不是u的後代,此時假定路徑上除v以外的每個節點都成為u的後代(否則可一次向前推進,以v的父節點進行假設),則假設v的父節點w為u的子節點,則有:w.f≤u.f,同時由於節點v必須在節點u被發現之後,同時在w節點完成處理之前被發現,因此有:u.d邊的分類:
深度搜尋可以對輸出圖g = (v, g)的邊進行分類分析,現在對邊具有的一些性質進行特定的分類和定義。
1、樹邊:為dff中gp中的邊,若v因演算法對邊(u, v)的探索首次被發現,則(u,v)為一條樹邊。
2、後向邊:後向邊(u, v)是將節點u連線到其在dff中乙個祖先節點v的邊。由於有向圖中可能存在自迴圈,自迴圈也被認為為後向邊。
3、前向邊:將節點u連線到其在樹中乙個後代節點v的邊(u, v)。
4、橫向邊:指其他所有邊。這些邊可以連線到同一節點,只要兩個節點不為對方的祖先,也可以連線不同dff樹中的兩個節點。
有人可能會對此處後向邊的定義感到疑惑:既然存在從祖先v到後代u的邊,那麼不是應該從直接從v連線到u?此處我給出問題的答案:
我們要注意到dfs是從根結點v出發,再到某乙個與它連線的結點u,再對這個結點u再進行dfs,那麼完全可能存在這樣一種情況:此時未被發現的結點w同時具有連線v和u的邊,那麼根據演算法的定義,我們就會展開一條 v - u - w 的路徑,而非分別由v到u或者w結點,此時,根據演算法的定義,我們會對w結點再進行dfs,此時由於根節點v是灰色的,因此我們將不會探索這條邊,因為在探索到這個結點後,我們將不會再單獨將其作為根再進行一次dfs——就算如此,我們也不會探查到路徑 w - v,此時這條邊(w, v)便是上述定義中的後向邊。
同時,筆者在閱讀完後面的內容後,認為此處前向邊的定義不嚴謹,對此進行一些說明:正常情況下,後代的概念是某個人的兒子及兒子的後代。(遞迴定義),然而,在此處的後代指的是除去親代後的後代,即孫子輩和孫子輩以後的人。否則將會和樹邊的定義發生衝突——樹邊指的是在樹中由父親到兒子的邊。
1、v為白色,則表明(u, v)是一條樹邊。
2、v為灰色,則表明 (u, v)是一條後向邊。由於灰色節點形成一條線性的後代鏈,灰色節點的數量總是比dff中最近被發現的節點的深度多1,而演算法總是從深度最深的灰色節點向前推進,因此從該節點回探通向另乙個灰色節點的邊所達到的是當前灰色節點的祖先。
3、v為黑色,則表明(u, v)是一條前向邊或者橫向邊。
在這種情況,當u.d證明:當u.du.d時,二者無血緣關係,則二者為橫向邊。
定理4:對無向圖g進行dfs中,只會出現樹邊和後向邊。
證明:假設(u, v) 是g的任意一條邊。假定u.du的探索,那麼(u, v)是後向邊。在此時仍然在掃瞄u的鄰接鍊錶,因此其仍為灰色的。
廣度優先搜尋 深度優先搜尋
前言 這幾天複習圖論演算法,覺得bfs和dfs挺重要的,而且應用比較多,故記錄一下。廣度優先搜尋 有乙個有向圖如圖a 圖a廣度優先搜尋的策略是 從起始點開始遍歷其鄰接的節點,由此向外不斷擴散。1.假設我們以頂點0為原點進行搜尋,首先確定鄰接0的頂點集合s0 2.然後確定頂點1的集合s1 頂點2沒有鄰...
廣度優先搜尋,深度優先搜尋
深度優先搜尋 depth first search 簡稱dfs。最直觀的例子就是 走迷宮 廣度優先搜尋 每個頂點都要進出一遍佇列,每個邊也都會被訪問一次,所以 時間複雜度o v e 主要消耗記憶體的是visited prev陣列 queue佇列,所以 空間複雜度o v 深度優先搜尋 每條邊最多會被訪...
深度優先搜尋 廣度優先搜尋
深度優先搜尋 廣度優先搜尋 通過鄰接矩陣對圖進行深搜和廣搜 package com.neusoft.data.structure 深度優先搜尋 廣度優先搜尋 通過鄰接矩陣對圖進行深搜和廣搜 public class dfsbfs 初始化 邊 mmatrix new int vlen vlen for...