上一篇博文咱們提到了圖的最短路徑問題:http://www.cnblogs.com/mm93/p/8434056.html。而最短路徑問題能夠說是這樣的一個問題:路已經修好了,該怎麼從這兒走到那兒?可是在和圖有關的問題中,還有另外一種有趣的問題:修路的成本已經知道了,該怎麼修路才能儘量節約成本,同時將這些地方都連起來?html
好比咱們知道有這麼幾個城市,它們互相之間尚未路:算法
通過實地考察後,發現能夠修的路以及各條路的修路成本以下:編程
可是咱們的預算有限,須要在修路時儘量的省錢(也就是儘可能減少全部邊的權重之和),同時保證圖中每個城市老是能到達圖中任意一個城市,該怎麼修路呢?對於上圖來講,其中一個方案是這樣的,其總共的修路成本(即總權重)爲8:數據結構
另外一個方案是這樣的,略有不一樣,不過總成本也是8:數據結構和算法
像這樣的問題,就是咱們今天要討論的最小生成樹問題。爲了更準確地說明什麼是最小生成樹,咱們須要先了解一個概念:連通。對於一個無向圖而言,若是每一個頂點到每一個其它頂點都存在路徑,則該無向圖是連通的。而對於有向圖而言,道理相同又稍有變化,在有向圖中,若每一個頂點到每一個其它頂點都存在可行的路徑,則該有向圖是強連通的。好比下圖就不是一個強連通的有向圖,其中非v0頂點沒法到達v0頂點:spa
可是若是咱們將上面這個有向圖的邊都變爲無向邊,咱們就會獲得一個無向圖,此無向圖即該有向圖的基礎圖(underlying graph)。若是一個有向圖非強連通,可是其基礎圖是連通的,咱們就稱該有向圖是弱連通的。上面這個有向圖就是一個弱連通的有向圖。設計
明白了什麼是連通以後,接下來咱們說說最小生成樹是什麼:在一個連通的無向圖的全部邊中,挑選出足以使全部頂點連通的那些邊,且這些邊的總權重不能更低,則這些邊與全部頂點構成的圖就是最小生成樹。「最小」的意思是其總權重是最小的,「生成」則是由於這個樹是從一個無向圖中找出來的,也即生成的。3d
等等_(:з」∠)_ 不是說「這些邊與全部頂點構成的圖」嗎,怎麼就成了樹?緣由是這樣的,若是一個無向圖是連通的,那麼咱們就能找出知足上述條件的那個圖,而若是那個圖存在,那它必定是一棵樹(樹是特殊的圖嘛,這一點應該要懂的),好比本文前面所找出的最小生成圖,顯然是一棵樹:htm
爲何稱最後找出來的頂點與邊的集合爲最小生成樹,咱們已經知道了,而爲何最後找出來的必定是樹……咱能不糾結嗎 ( ̄. ̄)blog
好了,接下來討論下一個問題:有向圖能夠找出最小生成樹嗎?答案是能夠,只要有向圖是強連通的。而且尋找有向圖的最小生成樹的過程也是基本同樣的,由於無向圖本就是以有向圖的形式存儲的(一條無向邊拆成兩條有向邊)。不過由於本文並不打算給出可運行的代碼,因此咱們的討論以無向圖爲基準,主要關注算法的思路,而且不考慮所給圖非連通的狀況。
想要在圖中找出最小生成樹,有兩種算法可供選擇:Prim算法和Kruskal算法。由於Prim算法與尋找最短路徑的Dijkstra算法很是很是很是像,因此咱們先來討論一下Prim算法。
Prim算法的思路是這樣的:
1.任選一個頂點,將其標爲已知,即表示該頂點已在樹中(Dijkstra算法中,起點由咱們指定)
2.找出全部已知頂點鄰接的未知頂點,其中與任一已知頂點的鄰接邊權重最小的未知頂點,咱們將其標爲已知,同時將其preV設爲與其鄰接邊最小的已知頂點,且其distance設爲該鄰接邊的權重(在Dijkstra算法中,咱們用的是「指向」,由於要考慮到有向圖的狀況,此外,Dijkstra算法中,咱們將被標爲已知的未知頂點的distance設爲與其相連的已知頂點的distance加上邊的權重)
3.反覆執行第二步,直至不存在已知頂點鄰接了未知頂點爲止。
抽象的說,Prim算法就是隨機選一個頂點,將其拉進原先爲空的樹中,而後不斷地經過儘量小的邊將其餘頂點拉進這棵樹中
老樣子,上述說法晦澀難懂 ( ̄. ̄)。因此咱們須要實際的走一遍來加深一下理解,如下圖爲例:
假設咱們以v3做爲起點,則圖初始化後的狀態以下(頂點旁有紅圈表示該頂點已知,紅圈中即該頂點的preV,頂點的distance咱們暫不考慮):
接着,咱們找出全部已知頂點(v3)鄰接的全部未知頂點:v0、v一、v二、v四、v五、v6。發現與已知頂點鄰接邊最小的未知頂點是v一、v4,其中未知頂點v1與已知頂點v3的鄰接邊權重爲1,未知頂點v4與已知頂點v3的鄰接邊權重也爲1,咱們任選其一便可,好比選擇v1,而後將v1設爲已知,v1.preV=v3:
繼續,咱們找出全部已知頂點(v一、v3)鄰接的全部未知頂點:v0、v二、v四、v五、v6,發現與已知頂點鄰接邊最小的未知頂點是v0、v4,其中未知頂點v0與已知頂點v1的鄰接邊權重爲1,未知頂點v4與已知頂點v1或v3的鄰接邊權重爲1,咱們任選其一,好比v4,而後將v4設爲已知,v4.preV=v1(也能夠是v4.preV=v3):
繼續,咱們找出全部已知頂點(v一、v三、v4)鄰接的全部未知頂點:v0、v二、v五、v6,發現與已知頂點鄰接邊最小的未知頂點是v0、v6,其中未知頂點v0與已知頂點v1的鄰接邊權重爲1,未知頂點v6與已知頂點v4的鄰接邊權重爲1,咱們選v6,將v6設爲已知,v6.preV=v4:
繼續,咱們找出全部已知頂點鄰接的全部未知頂點:v0、v二、v5,其中與已知頂點的鄰接邊最小的是v0,未知頂點v0與已知頂點v1的鄰接邊權重爲1,咱們將v0設爲已知,v0.preV=v1:
繼續,找出全部已知頂點鄰接的全部未知頂點:v二、v5,發現其中未知頂點v5與已知頂點v6的鄰接邊權重最小爲2,因此咱們將v5設爲已知,v5.preV=v6:
繼續,找出全部已知頂點鄰接的全部未知頂點:v2,其中未知頂點v2與已知頂點v5的鄰接邊權重最小,因此咱們將v2設爲已知,v2.preV=v5:
繼續,發現已經沒有哪一個已知頂點鄰接了未知頂點,因此算法結束。
接下來,咱們只要進行這兩步操做就能夠得出最小生成樹:
1.將每一個頂點與其preV相連的邊標爲已知
2.將非已知的邊刪去。
將每一個頂點與其preV相連的邊標爲已知(注意,v3的preV是自身,此狀況咱們不作任何操做便可):
刪去非已知的邊:
固然,在實際編程中,有可能並不會執行這兩個操做,咱們只要在將最小生成樹的相關信息保存在pathTable中便可,本例中算法結束後pathTable應爲以下(與Dijkstra算法使用的pathTable略有不一樣:沒有distance域):
固然,咱們也可使用和Dijkstra算法時同樣的pathTable,即加上distance域,不過在計算最小生成樹時,一個頂點的distance域應該是其與preV鄰接的邊的權重:
在咱們走一遍Prim算法時,咱們發現v4.preV既能夠設爲v3,也能夠設爲v1,這就已經說明了一點:一個圖的最小生成樹並不必定是惟一的。不過還要注意的是:一個圖即使有多個最小生成樹,它們的總權重也應該是同樣的。
若是你回顧一遍Prim算法和Dijkstra算法,就會發現,Prim算法與Dijkstra算法的區別能夠說就兩個:
1.Prim算法的「起點」是任選的,Dijkstra算法是給定的(畢竟要找的是單源最短路徑)
2.Prim算法在將一個未知頂點設爲已知時,其distance設爲其與已知頂點的最小鄰接邊的weight,而Dijkstra算法則是設爲已知頂點.distance+weight
換句話說,Prim算法就是稍稍修改了一下的Dijkstra算法。若是你仔細觀察咱們用Prim算法生成的樹,你會發現從v3出發到任意頂點的路徑剛好是v3到該頂點的最短路徑:
接下來本應討論Kruskal算法,可是我突然發現我以前忘了寫一篇關於樹的集合與不相交集的博文(⊙ˍ⊙)。若是要討論Kruskal算法,這兩個預備知識是必不可少的,而若是這兩個知識也要講解的話,博文就太長了Orz。因此我只簡單說說Kruskal的思路。
在Prim算法中,咱們是以已知頂點(即已在最小生成樹中的頂點)爲基礎,不斷地將未知頂點拉進樹中。而Kruskal算法則是另外一種思路:以當前最小的邊爲基礎,不斷地將未知頂點拉進樹中(這個過程可能產生多棵樹)。
如下圖爲例,咱們走一遍Kruskal算法:
首先,咱們須要將邊按權重從小到大排序,才能找「當前最小邊」:
先是處理當前最小邊[v0,v1],其所鏈接的兩個頂點均未知,因此咱們將它們均設爲已知,並連起來:
而後處理下一個當前最小邊[v1,v3],其所鏈接的v3未知,將v3設爲已知,連起來:
接着處理邊[v3,v4],其鏈接的v4未知,將v4設爲已知,連起來:
接着處理邊[v1,v4],其鏈接的兩個頂點均爲已知,故跳過
接着處理邊[v4,v6],其鏈接的v6未知,將v6設爲已知,連起來:
接着處理邊[v2,v5],其鏈接的v2和v5均爲未知,因此將v二、v5均設爲已知,連起來(注意,此時產生了兩棵樹):
接着處理邊[v5,v6],其鏈接的頂點均爲已知,可是v5和v6處於不一樣的樹,因此咱們將其連起來(這部分的相關判斷和處理須要樹的集合知識,以及不相交集數據結構):
接下來處理的全部邊都是所鏈接頂點已知,且所鏈接頂點處於同一棵樹中,因此均會跳過,而後算法結束。
沒有掌握住博文的順序和鋪墊,實在是失敗 ̄△ ̄
不過Kruskal算法的思路我想我應該講清楚了,就是Dijkstra算法和Prim算法的講解可能太生硬了一些,可是細細地讀、細細地理解、細細地過一遍,應該仍是能明白的◑ω◐
這個系列的博文的主體部分到這兒就算結束了,從第一篇博文一路看到這兒的話,基本的數據結構和算法應該都能掌握。而像什麼B+樹、紅黑樹、算法設計技巧等更特殊的知識我沒有算在主體部分中,之後可能會以「淺入淺出數據結構(附)」的標題形式寫出。