Kruskal算法


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也已經連通了(這里上圖的連通線用紅色表示了)。

最后就剩下EG和FG了。當然我們選擇了EG。最后成功的圖就是右:

 

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; //最短路徑 }
復制代碼

 

運行結果截圖:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM