在數據結構與算法的圖論中,(生成)最小生成樹算法是一種經常使用而且和生活貼切比較近的一種算法。可是可能不少人對概念不是很清楚。咱們看下百度百科對於最小生成樹定義:java
一個有 n 個結點的連通圖的生成樹是原圖的極小連通子圖,且包含原圖中的全部 n 個結點,而且有保持圖連通的最少的邊。 最小生成樹能夠用kruskal(克魯斯卡爾)算法或prim(普里姆)算法求出。算法
通俗易懂的講就是最小生成樹包含原圖的全部節點而只用最少的邊
和最小的權值距離
。由於n
個節點最少須要n-1
個邊聯通,而距離就須要採起某種策略選擇恰當的邊。數組
從定義上分析,最小生成樹實際上是一種能夠看做是樹的結構。而最小生成樹的結構來源於圖(尤爲是有環狀況)。經過這個圖咱們使用某種算法造成最小生成樹的算法就能夠叫作最小生成樹算法。具體實現上有兩種實現方法、策略分別爲kruskal
算法和prim
算法。數據結構
學習最小生成樹實現算法以前咱們要先高清最小生成樹的結構和意義所在。咱麼首先根據一些圖更好的祝你理解。ide
在中國城市道路規劃中,是一門很須要科學的研究(只是假設學習沒必要當真)。城市道路鋪設可能經歷如下幾個階段。函數
因此咱們要從有環圖中選取代價和最小的路線一方面代價最小(總距離最小最省黃金)另外一方面聯通全部城市。學習
然而根據上圖咱們能夠獲得如下最小生成樹: this
主要矛盾
對問題起着決定性做用。主要矛盾次要矛盾相互影響,相互滲透,必定程度能夠相互轉化。故咱們看問題要抓關鍵、找核心。而相似的還有局部區域島嶼聯通修橋,海底通道這些高成本的都多多少少會運用。spa
上面介紹了最小生成樹是什麼,可是咱們須要掌握和理解最小生成樹如何造成。給你一個圖,生成一個最小生成樹,固然須要必定規則。而在實現最小生成樹方面有prim和kruskal算法,這兩種算法的策略有所區別,可是時間複雜度一致。code
百度百科定義的基本思想:
先構造一個只含 n 個頂點、而邊集爲空的子圖,把子圖中各個頂點當作各棵樹上的根結點,以後,從網的邊集 E 中選取一條權值最小的邊,若該條邊的兩個頂點分屬不一樣的樹,則將其加入子圖,即把兩棵樹合成一棵樹,反之,若該條邊的兩個頂點已落在同一棵樹上,則不可取,而應該取下一條權值最小的邊再試之。依次類推,直到森林中只有一棵樹,也即子圖中含有 n-1 條邊爲止。
簡而言之,Kruskal
算法進行調度的單位是邊,它的信仰爲:全部邊能小則小,算法的實現方面和並查集(不相交集合)很像,要用到並查集判斷兩點是否在同一集合。
而算法的具體步驟爲:
q1
中。初始全部點相互獨立。q1
最小邊,判斷邊的兩點是否聯通。union
(並查集合並)將兩個頂點合併。這條邊被使用(能夠儲存或者計算數值)。q1
爲空。此時被選擇的邊構成最小生成樹。除了Kruskal
算法之外,普里姆算法(Prim
算法)也是經常使用的最小生成樹算法。雖然在效率上差很少。可是貪心的方式和Kruskal
徹底不一樣。prim算法的核心信仰是:從已知擴散尋找最小。它的實現方式和Dijkstra
算法類似但稍微有所區別,Dijkstra是求單源最短路徑。而每計算一個點須要對這個點重新更新距離。而prim甚至不用更新距離。直接找已知點的鄰邊最小加入便可!
對於具體算法具體步驟,大體爲:
q1
,設置一個boolean數組bool[]
標記該位置已經肯定。v1
並判斷邊另外一點p是否被標記(訪問),若是p
被標記說明已經肯定那麼跳過,若是未被標(訪問)記那麼標記該點p
,而且與p相連的未知點(未被標記)構成的邊加入集合q1
,邊v1(能夠進行計算距離之類,該邊構成最小生成樹) .大致步驟圖解爲:
由於prim從開始到結束一直是一個總體在擴散,因此不須要考慮兩棵樹合併的問題,在這一點實現上稍微方便了一點。
固然,要注意的是最小生成樹並不惟一,甚至同一種算法生成的最小生成樹均可能有所不一樣,可是相同的是不管生成怎樣的最小生成樹:
上面分析了邏輯實現。下面咱們用代碼簡單實現上述的算法。
package 圖論;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
public class prim {
public static void main(String[] args) {
int minlength=0;//最小生成樹的最短路徑長度
int max=66666;
String cityname[]= {"北京","武漢","南京","上海","杭州","廣州","深圳"};
int city[][]= {
{ max, 8, 7, max, max, max, max }, //北京和武漢南京聯通
{ 8, max,6, max,9, 8,max }, //武漢——北京、南京、杭州、廣州
{ 7, 6, max, 3,4, max,max }, //南京——北京、武漢、上海、杭州
{ max, max,3, max,2, max,max }, //上海——南京、杭州
{ max, 9,4, 2,max, max,10 }, //杭州——武漢、南京、上海、深圳
{ max, 8,max, max,max, max,2 }, //廣州——武漢、深圳
{ max, max,max, max,10,2,max }//深圳——杭州、廣州
};// 地圖
boolean istrue[]=new boolean[7];
//南京
Queue<side>q1=new PriorityQueue<side>(new Comparator<side>() {
public int compare(side o1, side o2) {
// TODO Auto-generated method stub
return o1.lenth-o2.lenth;
}
});
for(int i=0;i<7;i++)
{
if(city[2][i]!=max)
{
istrue[2]=true;
q1.add(new side(city[2][i], 2, i));
}
}
while(!q1.isEmpty())
{
side newside=q1.poll();//拋出
if(istrue[newside.point1]&&istrue[newside.point2])
{
continue;
}
else {
if(!istrue[newside.point1])
{
istrue[newside.point1]=true;
minlength+=city[newside.point1][newside.point2];
System.out.println(cityname[newside.point1]+" "+cityname[newside.point2]+" 聯通");
for(int i=0;i<7;i++)
{
if(!istrue[i])
{
q1.add(new side(city[newside.point1][i],newside.point1,i));
}
}
}
else {
istrue[newside.point2]=true;
minlength+=city[newside.point1][newside.point2];
System.out.println(cityname[newside.point2]+" "+cityname[newside.point1]+" 聯通");
for(int i=0;i<7;i++)
{
if(!istrue[i])
{
q1.add(new side(city[newside.point2][i],newside.point2,i));
}
}
}
}
}
System.out.println(minlength);
}
static class side//邊 {
int lenth;
int point1;
int point2;
public side(int lenth,int p1,int p2) {
this.lenth=lenth;
this.point1=p1;
this.point2=p2;
}
}
}
複製代碼
實現效果:
package 圖論;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
import 圖論.prim.side;
/* * 做者:bigsai(公衆號) */
public class kruskal {
static int tree[]=new int[10];//bing查集
public static void init() {
for(int i=0;i<10;i++)//初始
{
tree[i]=-1;
}
}
public static int search(int a)//返回頭節點的數值 {
if(tree[a]>0)//說明是子節點
{
return tree[a]=search(tree[a]);//路徑壓縮
}
else
return a;
}
public static void union(int a,int b)//表示 a,b所在的樹合併小樹合併大樹(不重要) {
int a1=search(a);//a根
int b1=search(b);//b根
if(a1==b1) {//System.out.println(a+"和"+b+"已經在一棵樹上");
}
else {
if(tree[a1]<tree[b1])//這個是負數,爲了簡單減小計算,不在調用value函數
{
tree[a1]+=tree[b1];//個數相加 注意是負數相加
tree[b1]=a1; //b樹成爲a的子樹,直接指向a;
}
else
{
tree[b1]+=tree[a1];//個數相加 注意是負數相加
tree[a1]=b1; //b樹成爲a的子樹,直接指向a;
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
init();
int minlength=0;//最小生成樹的最短路徑長度
int max=66666;
String cityname[]= {"北京","武漢","南京","上海","杭州","廣州","深圳"};
boolean jud[][]=new boolean[7][7];//加入邊須要防止重複 好比 ba和ab等價的
int city[][]= {
{ max, 8, 7, max, max, max, max },
{ 8, max,6, max,9, 8,max },
{ 7, 6, max, 3,4, max,max },
{ max, max,3, max,2, max,max },
{ max, 9,4, 2,max, max,10 },
{ max, 8,max, max,max, max,2 },
{ max, max,max, max,10,2,max }
};// 地圖
boolean istrue[]=new boolean[7];
//南京
Queue<side>q1=new PriorityQueue<side>(new Comparator<side>() {//優先隊列存邊+
public int compare(side o1, side o2) {
// TODO Auto-generated method stub
return o1.lenth-o2.lenth;
}
});
for(int i=0;i<7;i++)
{
for(int j=0;j<7;j++)
{
if(!jud[i][j]&&city[i][j]!=max)//是否加入隊列
{
jud[i][j]=true;jud[j][i]=true;
q1.add(new side(city[i][j], i, j));
}
}
}
while(!q1.isEmpty())//執行算法
{
side newside=q1.poll();
int p1=newside.point1;
int p2=newside.point2;
if(search(p1)!=search(p2))
{
union(p1, p2);
System.out.println(cityname[p1]+" "+cityname[p2]+" 聯通");
minlength+=newside.lenth;
}
}
System.out.println(minlength);
}
static class side//邊 {
int lenth;
int point1;
int point2;
public side(int lenth,int p1,int p2) {
this.lenth=lenth;
this.point1=p1;
this.point2=p2;
}
}
}
複製代碼
最小生成樹算法理解起來也相對簡單,實現起來也不是很難。Kruskal和Prim
主要是貪心算法的兩種角度。一個從總體開始找最小邊,遇到關聯不斷合併,另外一個從局部開始擴散找身邊的最小不斷擴散直到生成最小生成樹。在學習最小生成樹以前最好學習一下dijkstra
算法和並查集,這樣在實現起來可以快一點,清晰一點。
最後,若是你那天真的得到一大筆資金去修建這麼一條昂貴的黃金路線,能夠適當採起此方法,另外剩下的大批,,苟富貴,勿相忘。。
若是感受還行,還請點個贊,關注一下吧,關注筆者公衆號: bigsai
回覆數據結構便可得到數據結構的學習資料和視頻一份!