1. 程式人生 > >圖表算法—最小生成樹

圖表算法—最小生成樹

就是 art 完成 是否 接下來 重復 .cn info spa

1. 什麽是最小生成樹(Minimum Spanning Trees)

  對於一個無向圖,如果它的所有邊都帶有一定的數值(即帶權),則會變成下面的樣子

  技術分享圖片

  假設這些點都是城市,每個城市之間的連線代表城市間的道路,線上的數字代表著道路的長短。當然,修越長的道路就需要越多的資源。

  那麽如果要用最少的資源把所有城市都聯系起來(即任意城市A能沿著道路抵達任意城市B),我們應該怎樣建設道路呢?答案如下圖:

  技術分享圖片

  則就是最小生成樹:用最小的權值總和(即數值總和)把所有點都聯系起來。應註意到:最小生成樹的邊總數=此無向圖的點總數-1。

  註意,最小生成樹裏是不應該有閉合循環的,如:

  技術分享圖片

  權值為24的那條邊顯然是多余的。

2. 生成帶權的無向圖

  因為這種無向圖只比普通無向圖多了寫權值,我們只需在每條邊上多加一個變量來記錄權值即可。

  使用的鄰接列表也需要把權值信息寫上,如下圖:

  技術分享圖片

  目前有兩種比較主流的算法來找最小生成樹:kruskal‘s algorithm(克魯斯卡爾算法)和Prim‘s algorithm(普林演算法)。(中文譯名采用音譯的方法。)

  接下來,我們將逐一介紹這兩種算法。

 

3. kruskal‘s algorithm(克魯斯卡爾算法)

  從例子入手:

  技術分享圖片

  為了容易理解,我們把所有邊按權值大小排成遞增的數組。實際代碼操作時,只需要寫個方法,讓數組輸出一個最小值即可。

  技術分享圖片

  我們需要創建幾個數組:

  創建一個點的數組Points,把最小生成樹T的點存儲起來;

  創建一個邊的數組mst(Minimum spanning tree的簡寫)來把最小生成樹的邊都存儲起來。

  創建一個邊的數組pq把無向圖裏所有的邊都存儲起來。

  首先數組pq輸出並移除一個最小值:0-7 0.16。

  由於目前的最小生成樹T還沒有點,所以把0和7加進Points。

  把0-7這條邊加進mst。

  技術分享圖片

  然後數組pq輸出並移除一個最小值:2-3 0.17。

  檢查2和3是否在Points中。如果不在,則把這兩個點加入到Points中。

  把2-3這條邊加進mst。

  技術分享圖片

  然後數組pq輸出並移除一個最小值:1-7 0.19。

  7已經在Points中了,只需把1加進Points裏。

  把1-7這條邊加進mst。

  技術分享圖片

  然後數組pq輸出並移除一個最小值:0-2 0.26。

  由於0,2都已經在Points裏了,我們需要檢查如果把0-2這條邊加進最小生成樹裏是否會形成閉合循環。

  檢查方法稍後介紹。

  如果不會形成閉合循環,則把這條邊加進最小生成樹T中;否則,則無視這條邊,繼續看下一條邊。

  在這裏,不形成閉合循環,把0-2這條邊加進mst中。

  技術分享圖片

  然後數組pq輸出並移除一個最小值:5-7 0.28。

  7已經在Points中了,只需把5加進Points裏。

  把5-7這條邊加進mst。

  技術分享圖片

  

  然後數組pq輸出並移除一個最小值:1-3 0.29。

  由於1,3都已經在Points裏了,我們需要檢查如果把1-3這條邊加進最小生成樹裏是否會形成閉合循環。

  會形成閉合循環,無視之。

  然後數組pq輸出並移除一個最小值:1-5 0.32。

  由於1,5都已經在Points裏了,我們需要檢查如果把1-5這條邊加進最小生成樹裏是否會形成閉合循環。

  會形成閉合循環,無視之。

  然後數組pq輸出並移除一個最小值:2-7 0.34。

  2,7都已經在Points裏,且會形成閉合循環,無視之。

  然後數組pq輸出並移除一個最小值:4-5 0.35。

  5已經在Points中了,只需把4加進Points裏。

  把5-4這條邊加進mst。

  技術分享圖片

  然後數組pq輸出並移除一個最小值:2-1 0.36。

  2,1都已經在Points裏,且會形成閉合循環,無視之。

  然後數組pq輸出並移除一個最小值:4-7 0.37。

  4,7都已經在Points裏,且會形成閉合循環,無視之。

  然後數組pq輸出並移除一個最小值:4-0 0.38。

  4,0都已經在Points裏,且會形成閉合循環,無視之。

  然後數組pq輸出並移除一個最小值:6-2 0.4。

  2已經在Points中了,只需把6加進Points裏。

  把6-2這條邊加進mst。

  技術分享圖片

  此時,mst裏的邊數=無向圖的點總數-1。說明最小生成樹已經形成了,算法結束。

  現在討論如何檢測新加入的邊(v-w)是否會使最小生成樹形成閉合循環。假設現在最小生成樹的點總數為V。

  可以用深度優先搜索來檢測v是否能抵達w,如果可以,說明最小生成樹中已經有路連接v和w了,再加一條v-w會形成閉合循環。

  但是,還有一種方法更為高效:並查集算法。簡單總結一下就是:

  v與和v相連的所有點形成一個區域,此區域用一個點a來代表。

  w與和w相連的所有點形成一個區域,此區域用一個點b來代表。

  然後對比a是否等於b,如果是,則說明v,w處於同一區域,最小生成樹中已經有路連接v和w了,再加一條v-w會形成閉合循環;如果不是,說明v和w處於不同區域,可以加v-w進最小生成樹。

  總結一下kruskal‘s algorithm(克魯斯卡爾算法)的通用思路就是:

  把所有邊放進一個數組裏。

  然後數組輸出並移除一個擁有最小權值的邊,如果這條邊加入到最小生成樹內不會形成閉合循環,則把它加進去;否則無視它。

  如此循環,直到最小生成樹的邊總數=無向圖的點總數-1為止。

  順帶一提:輸出數值的最小值的方法可以把此數組做出最小堆的結構,然後輸出第一個元素即可。

代碼大概是這樣子的:

  技術分享圖片

4. Prim‘s algorithm(普林演算法)

  此算法有兩種實現版本:懶惰算法版(lazy implementation)和貪心算法版(eager implementation)。

  懶惰算法和貪心算法是兩種思想,貪心算法也被稱為過度熱情算法。

  從一個例子中感受一下:

  假設a在他的房間裏愉快地玩耍,突然他媽叫他收拾房間。此時a有兩個選擇:要麽馬上收拾,要麽等會再收拾。

  如果選擇馬上收拾,a會馬上打掃房間,甚至把走廊也掃了一遍。這就是貪心算法。

  如果選擇等會再收拾,a會等到他媽要來檢查的前一瞬間才開始收拾。這就是懶惰算法。  

  對於一個程序來說,貪心算法就是當程序收到一個指令時,它不但會完成指令,還會過度熱情地多做點其它事情;

  懶惰算法就是程序收到一個指令時,它會暫時無視之,等到我們要用到那個指令生成的結果時,程序才會去做這個指令。

  接下來,逐一介紹這兩個實現版本:

普林演算法之懶惰實現版本

  從例子入手:

  技術分享圖片

  我們需要創建幾個數組:

  創建一個點的數組Points,把最小生成樹T的點存儲起來;

  創建一個邊的數組mst(Minimum spanning tree的簡寫)來把最小生成樹的邊都存儲起來。

  創建一個邊的數組pq。

  首先,隨便找一個點開始:如0.

  把0加入到Points裏,把含點0的所有邊加入到pq裏。(為了方便理解,這裏的邊按權值遞增排進數組,實際操作只需從數組中輸出最小值,不必排序)

  技術分享圖片

  然後pq輸出並移除最小一個最小值:0-7 0.16。

  0已經在Points中了,只需把7加進Points裏。

  把含點7的除了邊的另一個端頂點已經在Points裏之外的所有邊加入到pq裏。

  技術分享圖片

  然後pq輸出並移除最小一個最小值:1-7 0.19。

  7已經在Points中了,只需把1加進Points裏。

  把含點1的除了邊的另一個端頂點已經在Points裏之外的所有邊加入到pq裏。

  技術分享圖片

  然後pq輸出並移除最小一個最小值:0-2 0.26。

  0已經在Points中了,只需把6加進Points裏。

  把含點2的除了邊的另一個端頂點已經在Points裏之外的所有邊加入到pq裏。

  技術分享圖片

  然後pq輸出並移除最小一個最小值:2-3 0.17。

  2已經在Points中了,只需把3加進Points裏。

  把含點3的除了邊的另一個端頂點已經在Points裏之外的所有邊加入到pq裏。

  技術分享圖片

  然後pq輸出並移除最小一個最小值:1-3 0.29。

  3,1都已經在Points裏,無視之。

  然後pq輸出並移除最小一個最小值:7-5 0.28。

  7已經在Points中了,只需把5加進Points裏。

  把含點5的除了邊的另一個端頂點已經在Points裏之外的所有邊加入到pq裏。

  技術分享圖片

  然後pq輸出並移除最小一個最小值:1-5 0.32。

  5,1都已經在Points裏,無視之。

  然後pq輸出並移除最小一個最小值:7-2 0.34。

  7,2都已經在Points裏,無視之。

  然後pq輸出並移除最小一個最小值:4-5 0.35。

  5已經在Points中了,只需把4加進Points裏。

  把含點4的除了邊的另一個端頂點已經在Points裏之外的所有邊加入到pq裏。

  技術分享圖片

  然後pq輸出並移除最小一個最小值:1-2 0.36。

  2,1都已經在Points裏,無視之。

  然後pq輸出並移除最小一個最小值:7-4 0.37。

  7,4都已經在Points裏,無視之。

  然後pq輸出並移除最小一個最小值:0-4 0.38。

  0,4都已經在Points裏,無視之。

  然後pq輸出並移除最小一個最小值:2-6 0.4。

  2已經在Points中了,只需把6加進Points裏。

  把含點6的除了邊的另一個端頂點已經在Points裏之外的所有邊加入到pq裏。(沒有可加的邊)

  技術分享圖片

  此時,mst裏的邊數=無向圖的點總數-1。說明最小生成樹已經形成了,算法結束。

  這個算法懶惰在哪呢?我們沿用那個收拾房間的例子。

  總結一下通用思路:

  1. 首先隨便選一個點加進Points

  2. 然後把含此點的除了邊的另一個端頂點已經在Points裏之外的所有邊加入到pq裏。(孩子a聽到要收拾房間,就把東西全部塞到房間裏了)

  3. 然後pq輸出並移除最小一個擁有最小值權值的邊,如果此邊的另一端點在Points裏,則無視之;如果不在,則把此點加進Points裏。(a的媽媽要來查房了,馬上收拾。)

  4. 重復2,3步直到mst裏的邊數=無向圖的點總數-1。

代碼實現:

  技術分享圖片

  技術分享圖片

  

普林演算法之貪心實現版本

  從例子入手:

  技術分享圖片

  我們需要創建幾個數組:

  創建一個點的數組Points,把最小生成樹T的點存儲起來;

  創建一個邊的數組mst(Minimum spanning tree的簡寫)來把最小生成樹的邊都存儲起來。

  創建一個的數組pq。

  首先,隨便找一個點開始:如0.

  把0加入到Points裏,把點0能直接去的點加入到pq裏。(為了方便理解,這裏的邊按權值遞增排進數組,實際操作只需從數組中輸出最小值,不必排序)

  技術分享圖片

  然後數組輸出並移除最小值:7. 把7對應的邊7-0加入到mst裏,把7加入Points裏。

  點7能直接去的、不在Points裏的點(1,2,5,4)加入到pq,但是2,4已經在pq裏了,7-2的權值0.34比pq裏面的0-2的權值大,故無視之;7-4的權值0.34比pq裏面的0-4的權值大,故無視之。

  技術分享圖片

  然後數組輸出並移除最小值:1. 把1對應的邊7-1加入到mst裏,把1加入Points裏。

  點1能直接去的、不在Points裏的點(3,2,5)加入到pq,但是2,5已經在pq裏了,1-2的權值0.36比pq裏面的0-2的權值大,故無視之;1-5的權值0.32比pq裏面的7-5的權值大,故無視之。

  技術分享圖片

  然後數組輸出並移除最小值:2. 把2對應的邊0-2加入到mst裏,把2加入Points裏。

  點2能直接去的、不在Points裏的點(3,6)加入到pq,但是3,6已經在pq裏了,3-2的權值0.17比pq裏面的1-3的權值小,故取代之;6-2的權值0.4比pq裏面的0-6的權值小,故取代之。

  技術分享圖片

  然後數組輸出並移除最小值:3. 把3對應的邊2-3加入到mst裏,把3加入Points裏。

  點3能直接去的、不在Points裏的點(6)加入到pq,但是6已經在pq裏了,3-6的權值0.52比pq裏面的6-2的權值大,故無視之。

  技術分享圖片

  然後數組輸出並移除最小值:5. 把5對應的邊7-5加入到mst裏,把5加入Points裏。

  點5能直接去的、不在Points裏的點(4)加入到pq,但是4已經在pq裏了,5-4的權值0.35比pq裏面的0-4的權值小,故取代之。

  技術分享圖片

  然後數組輸出並移除最小值:4. 把4對應的邊5-4加入到mst裏,把4加入Points裏。

  點4能直接去的、不在Points裏的點(6)加入到pq,但是6已經在pq裏了,4-6的權值0.93比pq裏面的6-2的權值大,故無視之。

  技術分享圖片

  然後數組輸出並移除最小值:6. 把6對應的邊6-2加入到mst裏,把6加入Points裏。

  點6沒有能直接去的、不在Points裏的點。最小生成樹生成完畢,結束算法。

  技術分享圖片

  

  總結一下通用思路:

  1. 首先隨便選一個點加進Points。

  2. 然後把這個點能直接去的、不在Points裏的點加入到數組pq中,把對應的那些邊記錄下來。如果要加的點已經存在於pq中,則看新加入的對應的邊權值是否比已存在的小,如果是則取代之;否則無視之。

  3. 數組輸出並移除擁有最小值的點,把這個點加進Points,這個點對應的邊加進mst裏。

  4. 重復2-3步,直到pq沒有元素為止。

  這個算法貪心在哪?

  與懶惰版對比一下就很顯然了: 第二步,懶惰版是直接把所有邊塞進數組裏,而貪心版是全部逐一比較(a會馬上打掃房間,甚至把走廊也掃了一遍)。

圖表算法—最小生成樹