今天更新這篇文章超級激動,因為我會最小生成樹的算法了(其實昨天就開始研究了,只是昨天參加牛客網的算法比賽,結果又被虐了,好難過~)
最小生成樹的算法,其實學了數據結構就會有一定的基礎,Kruskal算法是貪婪法的一種,一直在所有邊中選擇最小邊(當然不能形成環,因為最小生成樹是沒有環的)。首先遇到的問題就是如何表示這個圖,想用鄰接矩陣還是關聯矩陣。但是這兩種矩陣都要輸入好多,感覺太浪費空間了。於是,我自己定義了一個類,是邊的類。只要一個圖的每一條邊都關聯兩個點,兩個端點。於是這個類中包括一個關聯點的數組,是否被選擇,以及邊的權值。另外就是這個邊的代號,以a,b,c,d...來表示,頂點是int類型,以1,2,3,4...來表示。接下來來看實際的題目吧。
1.問題描述:
如下圖,求圖的最小生成樹,把邊選出來,並且求出最小生成樹的權值
每條邊的權值如下:
a -------------4
b--------------6
c--------------4
d--------------2
e--------------3
f---------------1
g--------------2
h--------------3
i---------------4
j---------------1
k--------------2
由圖利用Kruskal算法不難得出最小生成樹所選的邊是: f j g d h c / f j g d h c....
2.輸入:請輸入這個圖有幾條邊和頂點數:(11表示邊條數,7表示頂點個數) ,接下來3個數,分別為邊的權值,邊和哪兩個點關聯
11 7
4 1 2
6 2 4
4 2 3
2 1 4
3 1 5
1 4 5
2 4 6
3 3 6
4 5 6
1 5 7
2 6 7
3.輸出:
選的邊為: f j g d h c
權值是:13
4.算法思想:
輸入完了之后用一個排序方法,將樹的權值從小到大進行排序,保存在Bian[]這個數組中。接下來再從小到大進行選擇,判斷是否不在同一個連通分量和沒有被選擇。判斷不在一個連通分量很關鍵。這個思想,我昨天想了好久,開始是想選的邊不能形成圈,但發現這樣也太難判斷了,該怎么看是否形成圈呢?然后就各種百度,查資料,然后發現只要不在同一個聯通分量就可以了。比如假如上圖:我邊a權值是1,邊j權值是1,如果不在同一個連通分量,那么還是可以選擇的。化為計算機語言,也就是我把已經選擇了的邊的頂點保存在一個頂點數組里面,如果我接下來要添加的邊的兩個端點都已經在在這個數組里面,那么就不能選了,如果有一個在,一個不在,或者兩個都不在,就可以選。仔細想想是不是這個道理呢?但是我這樣做之后發現程序總是少選1條邊,原來,選到只剩最后一條邊的時候,是可能會兩個端點都在數組里的(當然有些圖也不會),於是在最后一條邊時需要另外判斷了。如果是最后一條邊,並且兩個點都在的話,那就選這條邊,else if 繼續前面那種選法就可以。
5.代碼示例:
import java.util.Scanner; class Edge{ int v; //邊的權值 int[] ConnectPoint = new int[2]; //邊所連接的點 int isSelect; //是否被選擇,1表示被選,0表示沒有被選 char No; //圖的編號,a,b,c,d...在創建圖的時候初始化的 } public class 最小生成樹2 { public static void main(String[] args) { Scanner scn = new Scanner(System.in); System.out.println("請輸入圖有幾條邊和幾個點:"); int n = scn.nextInt(); //保存邊數 int m = scn.nextInt(); //保存點數 Edge edge[] = new Edge[n]; for(int i=0;i<n;i++){ edge[i] = new Edge(); //創建出真實的邊出來 edge[i].v = scn.nextInt(); edge[i].ConnectPoint[0] = scn.nextInt(); edge[i].ConnectPoint[1] = scn.nextInt(); edge[i].No = (char) ('a' + i); } //輸入完了之后,將這些邊按權值進行排序 sort(edge); //定義一個點的數組,來存放已經選擇的邊的關聯頂點編號 int hasSelectPoint[] = new int[2*m]; //因為每條邊都有關聯兩個頂點,選擇的邊存放進來可能會存放兩次 //初始化,這個數組開始全部存0,都沒有被選擇 for(int i=0;i<hasSelectPoint.length;i++){ hasSelectPoint[i] = 0; } //權值排序完成之后就開始選邊 int j=0; int step = 1;//選的邊數,開始選第一條邊 for(int i=0;i<n;i++){ //最小生成樹要選的邊數等於頂點數-1,那么開始要選m-1最后一條邊時這樣選。再break退出 if(step == m-1 && allInSelectPoint(edge[i],hasSelectPoint)){ //最后一條邊了 edge[i].isSelect = 1;//直接選擇 break; }else if(edge[i].isSelect ==0 && !allInSelectPoint(edge[i],hasSelectPoint)){ //如果邊沒有被選,並且這條邊的兩個頂點不同時在頂點的數組里 edge[i].isSelect = 1; hasSelectPoint[j] = edge[i].ConnectPoint[0]; j++; hasSelectPoint[j] = edge[i].ConnectPoint[1]; j++; step++; //開始選第二條邊 } } //打印所選的邊 int sum = 0; // 計算權值 System.out.print("所選的邊為:"); for(int i=0;i<n;i++){ if(edge[i].isSelect==1){ sum = sum + edge[i].v; System.out.print(edge[i].No + " "); } } System.out.println(); System.out.println("權值為: " + sum); } private static boolean allInSelectPoint(Edge edge, int[] hasSelectPoint) { int flag1 = 0; int flag2 = 0;//這兩個變量是分別看邊的兩個端點在不在存放已經選擇點的數組里面 for(int i=0;i<hasSelectPoint.length;i++){ if(edge.ConnectPoint[0] ==hasSelectPoint[i]){ flag1 = 1; } if(edge.ConnectPoint[1] ==hasSelectPoint[i]){ flag2 = 1; } } if(flag1 ==1 && flag2 ==1){ //兩個點都在 return true; }else { //都不在或者有一個點不在 return false; } } //將權值進行交換 private static void sort(Edge[] edge) { Edge tempEdge = null; for(int i=0;i<edge.length;i++){ for(int j=i+1;j<edge.length;j++){ if(edge[j].v<edge[i].v){ tempEdge = edge[i]; edge[i] = edge[j]; edge[j] = tempEdge; } } } } }