1. 程式人生 > >演算法導論之圖的基本演算法

演算法導論之圖的基本演算法

圖是一種資料結構,有關圖的演算法是電腦科學中基礎性的演算法。這個論述恰如其分。

圖的基本演算法包括圖的表示方法和圖的搜尋方法。圖的搜尋技術是圖演算法領域的核心,有序地沿著圖的邊訪問所有頂點,可以發現圖的結構資訊。

1、圖的表示方法:

給定圖G=(V,E),其中V表示圖的點、E表示圖的邊,V[G]表示圖G的點集合,E[G]表示圖G的邊集合。圖的表示方法主要有鄰接表和鄰接矩陣兩類,均可用於有向圖和無向圖。有向圖,兩個頂點間有方向,是單向邊,而無向圖兩個頂點之間是雙向邊。

1)鄰接表

當圖G中|E|遠小於|V|2時,即為稀疏圖,適用鄰接表表示。圖G=(V,E)的鄰接表表示由一個包含|V|個列表的陣列Adj所組成,其中每個列表對應於V中的一個頂點。對於每一個u∈V,鄰接表Adj[u]包含所有滿足(u,v)∈E的頂點v。即Adj[u]中包含圖G中所有和頂點u相鄰的頂點,如果是有向圖,則是包含u指向的頂點。每個鄰接表中的頂點一般以任意順序儲存。

如果G是一個有向圖,則鄰接表的長度之和為|E|,u指向v為一條邊;如果G是一個無向圖,則鄰接表的長度之和為2|E|,u和v雙向兩條邊。

鄰接表的儲存空間為⊙(V+E),如果是圖邊是加權的,可以把權值w(u,v)儲存在相應陣列中表示。

2)鄰接矩陣

當圖G中|E|接近|V|2時,即為稠密圖,或者需快速判斷兩個頂點間是否存在連線邊時,適用鄰接矩陣表示。圖G=(V,E)的鄰接矩陣表示,假定各頂點編號從1,2,…,|V|,那麼G的鄰接矩陣為一個|V|X|V|的矩陣A=(aij),滿足:

aij=1,如果(i,j)∈E;aij=0,如果(i,j)∉E;在有向圖中屬於E[G]邊集合是從i到j的指向。

鄰接矩陣的儲存空間為⊙(V2

),與圖中的邊數無關。如果是無向圖,矩陣A的轉置矩陣是自己,可以只儲存對角線及對角線以上部分,儲存空間節省近一半。如果圖邊是加權的,可以在相應矩陣行列點上儲存權值。如果是非加權圖,儲存鄰接矩陣的每個元素時,可以只用一個二進位制位表示,而不必用一個字的空間。

2、圖的搜尋方法:

1)廣度優先搜尋

廣度優先搜尋(breadth-first search,BFS)的思想核心就是沿著點搜尋圖構建一顆廣度優先樹,發現圖結構資訊。給定圖G=(V,E)和特定源頂點S,BFS探索G的邊,發現所有S可達的頂點,並計算出S到達所有頂點之間的距離(最少邊數),對有向圖和無向圖都適用。BFS首先發現和S距離為k的所有頂點,在發現距離為k+1的其他頂點。BFS就是找出和u點存在邊關係的所有頂點後才會出發選擇下一個頂點。

重點描述下BFS演算法,很重要一點就是源點S到所有頂點的距離也得出,且是最短路徑最少邊數,這個演算法導論中給出了3個引理1個推論1個定理來證明。細細品味演算法導論中這些證明,有助於理解演算法,同時也有助於提升數學證明邏輯,也有助於理解普適性的思想,如動態規劃的分治和最優解。演算法導論中給出的演算法,一般行文結構是:定義、演算法描述、案例、效能分析、性質或正確性證明。正確性證明中就涉及到數學基礎知識基本公理基礎定義和歸納推理的證明方法及普適思想,反過來說,只有掌握數學才能更好設計具有優效能和正確性的計算機演算法。

BFS演算法的主要變數描述:

color[u],記錄頂點的狀態,用白色、灰色、黑色指示,未搜尋到頂點為白色,搜尋到但還未完全把有邊關係的頂點都搜尋到的為灰色,搜尋到所有有邊關係的頂點的黑色;

π[u],記錄u頂點的父頂點;

d[u],記錄源點s到頂點u的距離,最短路徑最少邊數;後面帶權的圖最短路徑就不是DFS演算法了。

圖G(V,E)用鄰接表儲存。

BFS_Func(G,s){

    //第一步:初始化所有頂點,不含源頂點s

for eachvertex u∈V[G]-{s} 

        do color[u]=white

           d[u]=∞

           π[u]=null

    //第二步:初始化源頂點s

    color[s]=gray

    d[u]=0

    π[u]=null

    //第三步:初始化先進先出佇列(FIFO),源頂點(白色變灰色)入佇列

    Q=null

    Enqueue(Q,s)

    //第四步:DFS搜尋

    while Q≠null  //還有灰色頂點

        do u=Dequeue(Q)  //出列,第一個是源頂點s

           for each v∈Adj[u]  //鄰接表中u頂點儲存的具有邊關係的頂點

         //白色頂點才入列,確保頂點只有一次入列機會,這個就是通過color這個指示變數來滿足

               do if color[v]=white 

                  then color[v]=gray //頂點變灰色,表明已搜尋到,但未搜尋其相關所有頂點

                       d[v]=d[u]+1  //距離加1

                       π[v]=u   //頂點v的父頂點是u

                       Enqueue(Q,v)

           color[u]=black //頂點u都搜尋完成,變黑色

}

每次閱讀演算法導論中的演算法,都感覺到精煉、清晰,相當到位。從演算法中不難得出DFS的時間執行效能是線性的,為O(V+E),雖然是雙層迴圈,但在迴圈中,頂點為白色只有一次機會,所以就是V[G]頂點數,而掃描所有鄰接表的次數就是圖的邊數E[G]。

最精彩的當然還是DFS取得的距離是最短路徑。這裡不贅述。

2)深度優先搜尋

深度優先搜尋(deepth-first search,DFS)的思想核心就是沿著邊搜尋圖構建深度優先樹林。DFS產生多個源頂點,形成多顆深度樹,從一個頂點出發,從鄰接表中發現第一條邊的相關頂點,接著探索相關頂點鄰接表中的邊和相關頂點,當最後一個頂點沒有邊時就結束,回溯父結點的新一條邊開始探索。DFS構建的深度樹互不相交。應該來說BFS適用於尋找最短路徑,而DFS適用於發現圖的結構資訊,如圖的邊型別。

DFS演算法中的括號定理,和編譯原理中語法分析思想一致。DFS給出一個括號定理和一個後裔區間的巢狀,從而得出白色路徑原理。定理、推論結合DFS設計的演算法都很好理解。

白色路徑定理:在一個有向圖或無向圖G=(V,E)的深度優先森林中,頂點v是頂點u的後裔,當且僅當在搜尋過程中在時刻d[u]發現u時,可以從頂點u出發,經過一條完全由白色頂點組成的路徑到達v。定理的證明中,採用了綜合法和分析法。

——綜合法是一種從題設到結論的邏輯推理方法,也就是由因導果的證明方法;

——分析法是一種從結論到題設的邏輯推理方法,也就是執果索因法的證明方法;分析法的證明路徑與綜合法恰恰相反。

關於括號定理和後裔區間定理以及白色路徑定理,都關係到演算法中很關鍵的一個指標變數設計,就是時間戳。這裡先談下DFS發現圖結構構建深度優先森林時關於邊的分類。邊的分類及性質是DFS一個重要應用點。

根據在圖G上進行深度優先搜尋所產生的深度優先森林Gπ,可以把圖的邊分類四種類型:

——樹邊(treeedge),是深度優先森林Gπ中的邊。如果頂點v是在探尋邊(u,v)時被首次發現,那麼(u,v)就是一條樹邊;

——反向邊(backedge),是深度優先樹中,連線頂點u到它的某一祖先頂點v的那些邊。有向圖中可能出現的自環也被認為是反向邊;

——正向邊(forwardedge)是指深度優先樹中,連線頂點u到它的某個後裔v的非樹邊(u,v)。

——交叉邊(crossedge)是其他型別的邊,存在於同一顆深度優先樹中的兩個頂點之間,條件是其中一個頂點不是另一個頂點的祖先。交叉邊也可以在不同的深度優先樹的頂點之間。

這樣的一個分類對著DFS演算法和案例才好理解,但看錶述也基本能明白。在DFS演算法中,可以對搜尋到的邊進行分類,分類核心思想是對於每條邊(u,v),當該邊被第一次搜尋到時,可根據所到達的頂點v的顏色:第一類白色的v頂點,表明是一條樹邊;第二類灰色的v頂點,表明是一條反向邊;第三類黑色的v頂點,表明是一條正向邊或交叉邊。這裡的理解關係到DFS演算法中另一個重要的指標變數設計,就是顏色。對於無向圖進行深度優先搜尋時邊的分類,引申出一個定理:在對一個無向圖G進行深度優先搜尋的過程中,G的每一條邊要麼是樹邊,要麼是方向邊。

有時對演算法導論中的定理和性質證明過程理解有點頭疼,不過細看下去收穫很多。以這個定理的證明來說,首先要掌握無向圖的性質(邊的雙向性質,在鄰接表中使儲存兩次),其次要理解DFS過程中邊的分類演算法,才能理解這個定理,從而應用該定理。

現在介紹下DFS演算法,首先描述下主要的變數:

π[v]表示頂點v的父節點;

color[v] 表示頂點v的狀態,開始為白色,第一次發現為灰色,結束該點所有邊的搜尋為黑色;

時間戳d[v]和f[v],在搜尋過程中頂點v第一次被發現時(設為灰色)所記錄的時間戳為d[v],當結束檢查v的鄰接表時(設為黑色)所記錄的時間戳為f[v];

時間戳的直在1和2|V|之間取整,對|V|個頂點中的每一個,都有一個發現時間和完成時間,d[v]<f[v],頂點v在時刻d[v]之前是白色,在時刻d[v]和f[v]之間是灰色,f[v]之後是黑色。這個關係就是之前定理和推論的重要基礎。

DFS_Func(G){

   //第一步初始化所有頂點

   for each vertex u∈V[G]

      do color[u]=white

         π[u]=null

         time=0;

   //第二步多源頂點出發深度搜索

   for each vertex u∈V[G]

      do if color[u]=white

           then DFS_Visit(u)

}

DFS_Visit_Func(u){

   Color[u]=gray //第一次發現,設為灰色

   time=time+1

   d[u]=time //發現事件的時間戳

   for each v∈Adj[u]  //找出邊(u,v)

      do if color[v]=white //頂點v第一次發現

         then π[v]=u //u是v的父結點

               DFS_Visit_Func(v)  //沿著邊找出頂點v的邊

       color[u]=black  //頂點u探索結束,設為黑色

       f[u]=time+1

}

結合圖示案例可以更好理解DFS演算法,尤其是遞迴呼叫。DFS演算法時間效能是⊙(V+E)。

深度優先搜尋的應用之一:拓撲排序。

對於有向無迴路圖,DFS執行中使沒有反向邊的,這個演算法導論中給出引理並證明。有向無迴路圖用於說明事件的先後次序,DFS後形成一個頂點序列,即是拓撲排序。對有向無迴路圖無反向邊的證明,採用了對命題的題設和結論的反證法。

深度優先搜尋的應用之二:強連通分支

有向圖G=(V,E)的一個強連通分支就是一個最大頂點集合C ⊆V,對C中的每一對頂點u和v,有uàv和vàu;即頂點u和v之間是互相可達的。強連通分支是一個有向無迴路圖,導論中有引理並證明。

在尋找圖G=(V,E)的強連通分支演算法中,提到了G的轉置,即GT=(V,ET),ET={(u,v):(v,u)∈E}。ET 就是有G中邊改變方向所得。在給定圖G的鄰接表表示情況下,建立GT 需要O(V+E)時間。G和GT有著完全相同的強連通分支。導論中證明了轉置後的圖GT可以計算出有向圖G的強連通分支。

把有向圖分解成強連通分支,再執行演算法,即先分治取得解再組合解。最重要的是強連通分支的定義和性質滿足解組合。