最小生成樹---Prim演算法和Kruskal演算法

来源:http://www.cnblogs.com/zhangming-blog/archive/2016/04/20/5414514.html
-Advertisement-
Play Games

Prim演算法 1.概覽 普里姆演算法(Prim演算法),圖論中的一種演算法,可在加權連通圖裡搜索最小生成樹。意即由此演算法搜索到的邊子集所構成的樹中,不但包括了連通圖裡的所有頂點(英語:Vertex (graph theory)),且其所有邊的權值之和亦為最小。該演算法於1930年由捷克數學家沃伊捷赫·亞爾尼 ...


Prim演算法

1.概覽

普里姆演算法Prim演算法),圖論中的一種演算法,可在加權連通圖裡搜索最小生成樹。意即由此演算法搜索到的邊子集所構成的樹中,不但包括了連通圖裡的所有頂點英語Vertex (graph theory)),且其所有邊的權值之和亦為最小。該演算法於1930年由捷克數學家沃伊捷赫·亞爾尼克英語Vojtěch Jarník)發現;併在1957年由美國電腦科學家羅伯特·普里姆英語Robert C. Prim)獨立發現;1959年,艾茲格·迪科斯徹再次發現了該演算法。因此,在某些場合,普里姆演算法又被稱為DJP演算法、亞爾尼克演算法或普里姆-亞爾尼克演算法。

 

2.演算法簡單描述

1).輸入:一個加權連通圖,其中頂點集合為V,邊集合為E;

2).初始化:Vnew = {x},其中x為集合V中的任一節點(起始點),Enew = {},為空;

3).重覆下列操作,直到Vnew = V:

a.在集合E中選取權值最小的邊<u, v>,其中u為集合Vnew中的元素,而v不在Vnew集合當中,並且v∈V(如果存在有多條滿足前述條件即具有相同權值的邊,則可任意選取其中之一);

b.將v加入集合Vnew中,將<u, v>邊加入集合Enew中;

4).輸出:使用集合Vnew和Enew來描述所得到的最小生成樹。

示例圖演示:

 

下麵對演算法的圖例描述:

圖例說明不可選可選已選(Vnew
 

此為原始的加權連通圖。每條邊一側的數字代表其權值。 - - -

頂點D被任意選為起始點。頂點ABEF通過單條邊與D相連。A是距離D最近的頂點,因此將A及對應邊AD以高亮表示。 C, G A, B, E, F D
 

下一個頂點為距離DA最近的頂點。BD為9,距A為7,E為15,F為6。因此,FDA最近,因此將頂點F與相應邊DF以高亮表示。 C, G B, E, F A, D
演算法繼續重覆上面的步驟。距離A為7的頂點B被高亮表示。 C B, E, G A, D, F
 

在當前情況下,可以在CEG間進行選擇。CB為8,EB為7,GF為11。E最近,因此將頂點E與相應邊BE高亮表示。 C, E, G A, D, F, B
 

這裡,可供選擇的頂點只有CGCE為5,GE為9,故選取C,並與邊EC一同高亮表示。 C, G A, D, F, B, E

頂點G是唯一剩下的頂點,它距F為11,距E為9,E最近,故高亮表示G及相應邊EG G A, D, F, B, E, C

現在,所有頂點均已被選取,圖中綠色部分即為連通圖的最小生成樹。在此例中,最小生成樹的權值之和為39。 A, D, F, B, E, C, G

 

3.簡單證明prim演算法

反證法:假設prim生成的不是最小生成樹

1).設prim生成的樹為G0

2).假設存在Gmin使得cost(Gmin)<cost(G0)   則在Gmin中存在<u,v>不屬於G0

3).將<u,v>加入G0中可得一個環,且<u,v>不是該環的最長邊(這是因為<u,v>∈Gmin)

4).這與prim每次生成最短邊矛盾

5).故假設不成立,命題得證.

 

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; //最短路徑 }

 

運行結果截圖:

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 版權聲明:本文為博主原創文章,如需轉載請註明出處。 準備 編譯好的Hotspot虛擬機 "Java虛擬機(HOTSPOT)源代碼編譯步驟" : GDB基本操作: "Linux編程基礎——GDB(入門)" "Linux編程基礎——GDB(設置斷點)" 系統: Ubuntu 14.04 調試步驟 可調試 ...
  • hibernate的Component,即組件,表示2個類之間的關係,即其中1個類可以作為另一個類的組件來使用。 1.先來看下annotation中關於component的API 2.2.2.3. 嵌入式對象(又名組件) 2.2.2.3. 嵌入式對象(又名組件) 2.2.2.3. 嵌入式對象(又名組 ...
  • 在實際項目中我使用antrun 和 closure-compiler壓縮JS項目。然後我就使用如下代碼: 首先加入依賴。 com.google.javascript closure-compiler v20160315 pom.xml文件加入插件代碼。 org.apache.maven.plugin... ...
  • 註:以下文章原文來自於Dr Charles Severance 的 《Python for Informatics》 11.3 組合查詢和抽取 如果我們想以“X-”字元串開頭的行中找到數字,就像下麵兩行字元串: X-DSPAM-Confidence: 0.8475X-DSPAM-Probabilit ...
  • CZPlayer CZPlayer是基於Qt開發的一款功能強大的音樂播放器,該播放器的論壇地址請點擊here,目前CZPlayer已經是第四個版本了,歷史版本也分別在我的github上, github地址:https://github.com/chxuan/CZPlayer.git,歡迎star和f ...
  • 今天,下了個模版,但是導進去的時候發現js會報錯。看了下其他都沒有錯誤。而有一個js報錯誤,請原諒我有點紅色強迫症,不能留一點紅色 。 錯誤如下:Syntax error on token "Invalid Regular Expression Options", no accurate corre ...
  • Java開發需要準備的東西?JDK+Eclipse 其中JDK的意思是Java開發工具包,Eclipse是進行用於做Java程式開發的工具(當然你也可以用記事本什麼的去做)。其他開發工具:JCreator,JBuilder,... jdk的介紹和安裝教程度娘裡面到處都是,這裡自己也在啰嗦一下吧。 關 ...
  • ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...