最小生成樹
所謂最小生成樹,就是一個圖的極小連通子圖,它包含原圖的所有頂點,並且所有邊的權值之和盡可能的小。
首先看看第一個例子,有下面這樣一個帶權圖:
它的最小生成樹是什么樣子呢?下圖綠色加粗的邊可以把所有頂點連接起來,又保證了邊的權值之和最小:
去掉那些多余的邊,該圖的最小生成樹如下:
下面我們再來看一個更加復雜的帶權圖:
同樣道理,下圖綠色加粗的邊可以把所有頂點連接起來,又保證了邊的權值之和最小:
去掉那些多余的邊,該圖的最小生成樹如下:
圖的極小連通子圖不需要回路,而是一個樹形結構,所以人們才把它叫做最小生成【樹】。圖的最小生成樹也不是唯一的,同一個圖可以有多個不同的最小生成樹,但是他們的權值之和是一樣的。
最小生成樹的用處可多了,比如我們要在若干個城市之間鋪設道路,而我們的預算又是有限的,那么我們就需要找出成本最低的方式鋪路,最小生成樹的作用就來了。
怎樣鋪設才能保證成本最低呢?
城市之間的交通網就像一個連通圖,我們並不需要在每兩個城市之間都直接進行連接,只需要一個最小生成樹,保證所有的城市都有鐵路可以觸達即可。
通常生成最小生成樹常用的算法有兩種,一種是 Kruskal 算法,另一種是 Prim 算法。下面介紹下 Prim 算法
Prim算法是如何工作的呢?
這個算法是以圖的頂點為基礎,從一個初始頂點開始,尋找觸達其他頂點權值最小的邊,並把該頂點加入到已觸達頂點的集合中。當全部頂點都加入到集合時,算法的工作就完成了。Prim算法的本質,是基於貪心算法。
接下來說一說最小生成樹的存儲方式。我們最常見的樹的存儲方式,是鏈式存儲,每一個節點包含若干孩子節點的指針,每一個孩子節點又包含更多孩子節點的指針:
這樣的存儲結構很清晰,但是也相對麻煩。為了便於操作,我們的最小生成樹用一維數組來表達,數組下標所對應的元素,代表該頂點在最小生成樹當中的父親節點。(根節點沒有父親節點,所以元素值是-1)
下面讓我們來看一看算法的詳細過程:
1.選擇初始頂點,加入到已觸達頂點集合。
2.從已觸達頂點出發,尋找到達新頂點的權值最小的邊。顯然從0到2的邊權值最小,把頂點2加入到已觸達頂點集合,Parents當中,下標2對應的父節點是0。
3.從已觸達頂點出發,尋找到達新頂點的權值最小的邊。顯然從2到4的邊權值最小,把頂點4加入到已觸達頂點集合,Parents當中,下標4對應的父節點是2。
4.從已觸達頂點出發,尋找到達新頂點的權值最小的邊。顯然從0到1的邊權值最小,把頂點1加入到已觸達頂點集合,Parents當中,下標1對應的父節點是0。
5.從已觸達頂點出發,尋找到達新頂點的權值最小的邊。顯然從1到3的邊權值最小,把頂點3加入到已觸達頂點集合,Parents當中,下標3對應的父節點是1。
這樣一來,所有頂點都加入到了已觸達頂點集合,而最小生成樹就存儲在Parents數組當中。
Java:
import java.io.*; import java.util.*; class Test { final static int INF = Integer.MAX_VALUE; public static int[] prim(int[][]matrix){ List <Integer> reachedVertexList = new ArrayList<Integer>();
//選擇頂點0為初始頂點,放入已觸達頂點集合中 reachedVertexList.add(0);
//創建最小生成樹數組,首元素設為-1 int[] parents = new int[matrix.length]; parents[0]=-1;
//邊的權重 int weight;
//源頂點下標 int fromIndex = 0;
//目標頂點下標 int toIndex =0; while(reachedVertexList.size() < matrix.length){ weight =INF;
//在已觸達的頂點中,尋找到達新頂點的最短邊 for(Integer vertexIndex :reachedVertexList){ for(int i =0;i <matrix.length;i++){ if(!reachedVertexList.contains(i)){ if(matrix[vertexIndex][i]<weight){ fromIndex =vertexIndex; toIndex =i; weight =matrix[vertexIndex][i]; } } } } //確定了權值最小的目標頂點,放入已觸達頂點集合 reachedVertexList.add(toIndex);
//放入最小生成樹的數組 parents[toIndex]=fromIndex; } return parents; } public static void main(String[]args){ int[][]matrix =new int[][]{ {0,4,3,INF,INF}, {4,0,8,7,INF}, {3,8,0,INF,1}, {INF,7,INF,0,9}, {INF,INF,1,9,0}, }; int[]parents =prim(matrix); System.out.println(Arrays.toString(parents)); } }
運行結果:
[-1, 0, 0, 1, 2]
這段代碼當中,圖的存儲方式是鄰接矩陣,在main函數中作為測試用例的圖和對應的鄰接矩陣如下: