Kruskal算法
1.概覽
Kruskal算法是一種用來尋找最小生成樹的算法,由Joseph Kruskal在1956年發表。用來解決同樣問題的還有Prim算法和Boruvka算法等。三種算法都是貪婪算法的應用。和Boruvka算法不同的地方是,Kruskal算法在圖中存在相同權值的邊時也有效。
2.算法簡單描述
1).記Graph中有v個頂點,e個邊
2).新建圖Graphnew,Graphnew中擁有原圖中相同的e個頂點,但沒有邊
3).將原圖Graph中所有e個邊按權值從小到大排序
4).循環:從權值最小的邊開始遍歷每條邊 直至圖Graph中所有的節點都在同一個連通分量中
if 這條邊連接的兩個節點於圖Graphnew中不在同一個連通分量中
添加這條邊到圖Graphnew中
示例圖演示:

圖例描述:

首先第一步,我們有一張圖Graph,有若干點和邊

將所有的邊的長度排序,用排序的結果作為我們選擇邊的依據。這里再次體現了貪心算法的思想。資源排序,對局部最優的資源進行選擇,排序完成后,我們率先選擇了邊AD。這樣我們的圖就變成了右圖

在剩下的變中尋找。我們找到了CE。這里邊的權重也是5

依次類推我們找到了6,7,7,即DF,AB,BE。

下面繼續選擇, BC或者EF盡管現在長度為8的邊是最小的未選擇的邊。但是現在他們已經連通了(對於BC可以通過CE,EB來連接,類似的EF可以通過EB,BA,AD,DF來接連)。所以不需要選擇他們。類似的BD也已經連通了(這里上圖的連通線用紅色表示了)。
3.簡單證明Kruskal算法
對圖的頂點數n做歸納,證明Kruskal算法對任意n階圖適用。
歸納基礎:
n=1,顯然能夠找到最小生成樹。
歸納過程:
假設Kruskal算法對n≤k階圖適用,那么,在k+1階圖G中,我們把最短邊的兩個端點a和b做一個合並操作,即把u與v合為一個點v',把原來接在u和v的邊都接到v'上去,這樣就能夠得到一個k階圖G'(u,v的合並是k+1少一條邊),G'最小生成樹T'可以用Kruskal算法得到。
我們證明T'+{<u,v>}是G的最小生成樹。
用反證法,如果T'+{<u,v>}不是最小生成樹,最小生成樹是T,即W(T)<W(T'+{<u,v>})。顯然T應該包含<u,v>,否則,可以用<u,v>加入到T中,形成一個環,刪除環上原有的任意一條邊,形成一棵更小權值的生成樹。而T-{<u,v>},是G'的生成樹。所以W(T-{<u,v>})<=W(T'),也就是W(T)<=W(T')+W(<u,v>)=W(T'+{<u,v>}),產生了矛盾。於是假設不成立,T'+{<u,v>}是G的最小生成樹,Kruskal算法對k+1階圖也適用。
由數學歸納法,Kruskal算法得證。
下面使用java程序演示Prim算法:
代碼如下:
package com.itheima.primer;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* 最小生成樹(普里姆算法(Prim算法))
* @author zhangming
* @date 2016/04/20
*/
public class MinTreePrimer {
private static List<Vertex> visitedVertexs,leftedVertexs; //分別為添加到集合U中的節點集和剩余的集合V中的節點集
private static List<Edge> searchEdges;
//初始化圖的信息
public static void initGraph(Graph g){
visitedVertexs = new ArrayList<Vertex>();
leftedVertexs = new ArrayList<Vertex>();
searchEdges = new ArrayList<Edge>();
Scanner sc = new Scanner(System.in);
System.out.print("輸入頂點數: ");
int vertexNumber = sc.nextInt();
System.out.print("請輸入邊數: ");
int edgeNumber = sc.nextInt();
String[] allVertex = new String[vertexNumber];
String[] allEdge = new String[edgeNumber];
System.out.println("=================================");
System.out.println("請輸入各個頂點:");
Scanner scanner = new Scanner(System.in);
for(int i=0;i<vertexNumber;i++){
System.out.print("頂點"+(i+1)+":");
allVertex[i] = scanner.nextLine();
}
System.out.println("=================================");
for(int i=0;i<edgeNumber;i++){
System.out.print("輸入邊(Vi,Vj)中的頂點名稱和權值W(如:A B 7): ");
allEdge[i] = scanner.nextLine();
}
g.vertex = new Vertex[allVertex.length];
g.edge = new Edge[allEdge.length];
g.minWeight = 0;
for(int i=0;i<allVertex.length;i++){
g.vertex[i] = new Vertex();
g.vertex[i].vName = allVertex[i];
leftedVertexs.add(g.vertex[i]); //初始化剩余點集合
}
for(int i=0;i<allEdge.length;i++){
g.edge[i] = new Edge();
g.edge[i].startVertex = new Vertex();
g.edge[i].endVertex = new Vertex();
String edgeInfo[] = allEdge[i].split(" ");
g.edge[i].startVertex.vName = edgeInfo[0];
g.edge[i].endVertex.vName = edgeInfo[1];
g.edge[i].weight = Integer.parseInt(edgeInfo[2]);
}
}
public static void onChangeVertex(Vertex vertex){
visitedVertexs.add(vertex); //添加初始節點,作為默認的開始節點
leftedVertexs.remove(vertex);
}
public static Vertex findOneVertex(Graph g){
int minValue = Integer.MAX_VALUE;
Vertex findVertex = new Vertex();
Edge findEdge = new Edge();
for(int i=0;i<visitedVertexs.size();i++){
for(int j=0;j<leftedVertexs.size();j++){
Vertex v1 = visitedVertexs.get(i);
Vertex v2 = leftedVertexs.get(j); //獲取兩個頂點的名稱
for(int k=0;k<g.edge.length;k++){
String startName = g.edge[k].startVertex.vName;
String endName = g.edge[k].endVertex.vName;
if((v1.vName.equals(startName) && v2.vName.equals(endName))
||(v1.vName.equals(endName) && v2.vName.equals(startName))){
if(g.edge[k].weight < minValue){
findEdge = g.edge[k];
minValue = g.edge[k].weight;
if(leftedVertexs.contains(v1)){ //會調用對象的equals方法比較對象,需重寫equals方法
findVertex = v1;
}else if(leftedVertexs.contains(v2)){
findVertex = v2;
}
}
}
}
}
}
g.minWeight+= minValue;
searchEdges.add(findEdge);
return findVertex;
}
public static void prim(Graph g){
while(leftedVertexs.size()>0){ //直到剩余節點集為空時結束循環
Vertex findVertex = findOneVertex(g);
onChangeVertex(findVertex);
}
System.out.print("\n最短路徑包含的邊: ");
for(int i=0;i<searchEdges.size();i++){
System.out.print("("+searchEdges.get(i).startVertex.vName+","+searchEdges.get(i).endVertex.vName+")"+" ");
}
System.out.println("\n最短路徑長度: "+g.minWeight);
}
public static void main(String[] args) {
Graph g = new Graph();
initGraph(g);
onChangeVertex(g.vertex[0]);
prim(g);
}
}
/**
* 頂點類Vertex
*/
class Vertex{
String vName; //頂點的名稱
@Override
public boolean equals(Object obj) {
if(obj instanceof Vertex){
Vertex vertex = (Vertex)obj;
return this.vName.equals(vertex.vName);
}
return super.equals(obj);
}
}
/**
* 邊類Edge
*/
class Edge{
Vertex startVertex;
Vertex endVertex;
int weight;
}
/**
* 圖的存儲結構
*/
class Graph{
Vertex[] vertex; //頂點集
Edge[] edge; //邊集
int minWeight; //最短路徑
}
運行結果截圖:


