最小生成樹:Prim演算法
講構建最小生成樹的演算法前,先來回顧一下什麼是生成樹,什麼是最小生成樹?生成樹是指:在一個連通圖Graph中,包含Graph中所有n個頂點的極小連通子圖,一定包含Graph中的n-1條邊(如果小於n-1條邊,生成樹會不連通。如果大於n-1條邊,就一定會產生迴路,那麼就不是樹了,樹中不能產生迴路)。且生成樹不唯一。舉個例子:
這個無向連通圖的生成樹分別有
什麼是最小生成樹?
- 無迴路,且包含原圖中的n-1個頂點。
- 包含原圖中的全部頂點。
- 邊的權重和在所有其他生成樹中最小。
- 最小生成樹存在,則該圖一定連通。反過來一樣,圖連通,則最小生成樹一定存在。
那麼如何構建滿足以上條件的生成樹?這篇日誌先介紹其中一種常用的普里姆(Prim)演算法。Prim演算法構建最小生成樹,簡單來說就是在圖中,從某一頂點出發,逐步構建,讓一棵小樹逐漸長大。用一個例子來說明更清晰點吧!首先看下面一張無向網圖:
要構造這張圖的最小生成樹,首先,假設我們從V0頂點開始出發,也就是以V0為根結點開始建樹,接著往外擴充套件,從與V0頂點相鄰的頂點中找出權值最小的頂點,可以看到是V6,所以把V6和V0連線起來。
也就是把V6收錄進了這棵最小生成樹中了。接著繼續,從當前樹中頂點的鄰接點中(也就是V0和V6的鄰接點中,找出權值最小的頂點)。可以看到是V1,所以把V1也接入最小生成樹中。
然後繼續,到V2結點,也接入最小生成樹中
因為不能形成迴路,所以V2和V6之間不能連線(雖然權值最小,等於3)。V0和V1之間也不能連線。所以只能V6和V4連線
最後V4和V5連線,V4再和V6連線,就完成了這棵最小生成樹的構建了。
(最小生成樹)
以上就是Prim演算法構建最小生成樹的過程。接下來看怎麼用程式碼實現這個演算法:
首先傳進去我們的圖MyGraph,和一個MST圖,MST圖用來存最小生成樹。然後定義 兩個一維陣列dist和parent,
· parent陣列存的是某個頂點的(下標表示)父結點parent[0]=-1表示V0為根結點。
· dist陣列用來儲存頂點Vj到VT頂點的邊的最小權值(也就是某個結點到最小生成樹MST的最小 距離),即儲存各頂點與當前樹的"距離".如果Vj屬於當前樹,則dist[j]=0;。
然後是變數TotalWeight儲存最小生成樹的權重和。Vcount儲存收錄的頂點的個數,以及一條邊E後面做構建生成樹時插入頂點用。
這些變數都定義完後,就要開始對它們進行初始化。第142行開始初始化dist陣列,預設初始點下標是0,也就是頂點V0到各個頂點的距離。初始化parent陣列,暫時定義所有頂點的父結點都是初始點0,因為V0是根節點,一開始就它一個。權重和TotalWeight和收錄的頂點個數Vcount都初始化為0。
然後初始化最小生成樹MST圖,就跟初始化一個新圖一樣,建立有Vertex個頂點但沒有邊的圖(還是用鄰接矩陣的方式)。第154行建立一個空的邊結點,後面做樹生長時插入頂點用。
初始化工作做完後,接著開始建樹了(第157行開始),首先是初始點0(假設從V0頂點開始),把V0頂點收錄進MST,因為結點V0是根結點,所以到最小生成樹的距離更新為0(即dist[0]=0)。收錄的頂點數Vcount++。parent[0]=-1是因為V0是根結點。
根結點處理好後,就要開始讓小樹生長了。我們可以用一個while迴圈,不斷找出當前最小生成樹的所有鄰接點中“距離”最近的(也就是權值最小)的頂點,找到一個就把它插入進MST裡,讓最小生成樹“生長”。那麼迴圈什麼時候結束?當然就是(第165行),當這個頂點找不到的時候就break退出while迴圈。
我們來看第163行開始,用一個FindMinDist函式找出未被收錄的結點中距離當前樹"距離"(即權值)最小的頂點賦給V。然後將V及相應的邊 <parent[V], V>收錄進MST(也就是說將頂點V和V的父結點插入到最小生成樹中)。第176行,插入完這個新頂點後,更新TotalWeight權值和。以及結點V到最小生成樹的距離更新為0(因為這個頂點已經融入到最小生成樹當中了)。Vcount++表示收錄到的頂點數加一。
迴圈的最後一步就是掃描圖上所有頂點,找出跟V相鄰的頂點,V的鄰接點,且是沒被收錄進最小生成樹中的V的鄰接點(dist[W]!=0即沒被收錄進最小生成樹中,MyGraph->[V][W]<INFINITY即W是V鄰接點)。第185行如果收錄了V後能使得dist[W]變小,那麼就更新一下dist[W]。
最後的最後,在執行完Prim演算法的最後一步,就是判斷下最小生成樹MST中的頂點個數有沒有達到原圖中的頂點個數。如果沒有達到,證明這個圖不連通,就返回ERROR。構建成功就返回最小生成樹的權重和TotalWeight。
還有一個函式FindMinDist函式要怎麼做呢?
FindMinDist函式找出未被收錄的頂點中dist最小值(即未被收錄的頂點中距離當前最小生成樹"距離"最短的頂點)。我們把原圖MyGraph和dist陣列傳進去,定義兩個變數MinIndex和V,MinIndex儲存最小頂點的下標,初始化它為INFINITY。找未被收錄的頂點中dist最小的頂點,就用一個迴圈掃描圖中所有頂點,如果頂點沒被收錄,且頂點值小於MinDist的,就更新一下MinDist,並且把下標V更新給MinIndex。如果能夠找到dist最小頂點的話,就把MinIndex返回出去,否則返回ERROR。
最後看下用鄰接矩陣方式的Prim演算法效果圖: