數據結構 最小生成樹 Kruskal算法


數據結構 最小生成樹 Kruskal算法

無向連通圖的生成樹不是唯一的。連通圖的一次遍歷所經過的邊的集合及圖中所有頂點的集合就構成了該圖的一棵生成樹,對連通圖的不同遍歷,就可能得到不同的生成樹,那么,它的所有生成樹中必有一棵邊的權值總和最小的生成樹,我們稱這棵生成樹為最小生成樹,簡稱為最小生成樹

最小生成樹:
具有最小權重且連接到所有結點的樹

對於帶權無向圖G=(V, E),V表示圖中的結點,E表示圖中的邊,最小生成樹為集合T, T是以最小代價連接V中所有頂點所用邊E的最小集合(就是聯通圖G中所有結點所需要的邊長度總和最小)。 集合T中的邊能夠形成一顆樹,因為每個節點(除根節點)都能向上找到它的一個父節點,這些邊加上V就構成圖G的一顆最小生成樹

安全邊:
假設A是某棵最小生成樹的子集,下一步我們需要做的就是找出一條邊(u,v),將其加入到A中,使得A∪{(u,v)}也是某棵最小生成樹的子集,我們稱(u,v)為安全邊
切割(S,V-S):
—對集合V的一個划分,將集合V划分為兩個部分S和V-S
橫跨切割:
—若邊(u,v)的一個節點屬於S另一個節點屬於V-S,則稱(u,v)橫跨切割
尊重集合A:
如果A中不存在橫跨切割的邊,則稱該集合尊重集合A

橫跨尊重集合A的切割(S,V-S)的一條輕量級邊(u,v)為A的一條安全邊

Prim算法和Kruskal算法在實現方面的區別:
1、Kruskal算法在生成最小生成樹的過程中產生的是森林,
Prim算法在執行過程中始終都是一棵樹;
2、Kruskal和Prim實現上的最大區別:
Kruskal不需要搜索每個頂點的鄰接節點,
Prim圖構建時需要利用鄰接鏈表進行構建,Kruskal不用!
3、Kruskal算法在效率上要比Prim算法快,
Kruskal只需要對權重邊做一次排序
而Prim算法則需要做多次排序

Prim算法是依賴於點的算法:
它的基本原理是從當前點尋找一個離自己(集合)最近的點然后把這個點拉到自己家來(距離設為0),同時輸出一條邊,並且刷新到其他點的路徑長度。俗稱,刷表。
根據Prim算法的特性可以得知,它很適合於點密集的圖。對Prim算法通常采用鄰接矩陣的儲存結構。這種儲存方法空間復雜度N^2,時間復雜度N^2。
對於稍微稀疏一點的圖,其實我們更適合采用鄰接表的儲存方式,可以節省空間,並在一定條件下節省時間。

Kruskal算法是依賴邊的算法:
基本原理是將邊集數組排序,然后通過維護一個並查集來分清楚並進來的點和沒並進來的點,依次按從小到大的順序遍歷邊集數組,如果這條邊對應的兩個頂點不在一個集合內,就輸出這條邊,並合並這兩個點。

根據Kruskal算法的特性可得,在邊越少的情況下,Kruskal算法相對Prim算法的優勢就越大。同時,因為邊集數組便於維護,所以Kruskal在數據維護方面也較為簡單,不像鄰接表那樣復雜。Kruskal算法的速度與點數無關,對於稀疏圖可以采用Kruskal算法。

Kruskal算法(克魯斯卡爾算法)

算法原理:
首先將每個頂點放入其自身的數據集合中。然后,按照權值的升序來選擇邊。當選擇每條邊時,判斷定義邊的頂點是否在不同的數據集中。如果是,將此邊插入最小生成樹的集合中,同時,將集合中包含每個頂點的聯合體取出,如果不是,就移動到下一條邊。重復這個過程直到所有的邊都探查過

Kruskal在找最小生成樹結點之前,需要對所有權重邊做從小到大排序。將排序好的權重邊依次加入到最小生成樹中,如果加入時產生回路就跳過這條邊,加入下一條邊。當所有結點都加入到最小生成樹中之后,就找出了最小生成樹

算法描述:
1、新建圖G,把圖G中所有的邊按照權值從小到大排序。(一般使用優先隊列)
2、取出最小的邊,如果這條邊連接的兩個節點於圖G中不在同一個連通分量中,則添加這條邊到圖G中.(使用並查集)
3、重復步驟2,直至圖G中所有的節點都在同一個連通分量中

性能分析
Kruskal算法,基本上取決於優先隊列的選擇,以及並查集的實現。
算法效率為 O(ElogE) E為圖中的邊數

時間復雜度:O(Elog2E) E為圖中的邊數
//對優先隊列和並查集 //查找算法之順序、二分、二叉搜索樹、紅黑樹 和並查集

import java.io.IOException;
import java.util.Scanner;

public class Kruskal {
    private int mEdgNum;        // 邊的數量
    private char[] mVexs;       // 頂點集合
    private int[][] mMatrix;    // 鄰接矩陣
    private static final int INF = 100;//Integer.MAX_VALUE; // 最大值

    //創建圖(用已提供的矩陣)
    public Kruskal(char[] vexs, int[][] matrix) {// vexs-- 頂點數組 ,matrix-- 矩陣(數據) 
        // 初始化"頂點數"和"邊數"
        int vlen = vexs.length;
        // 初始化"頂點"
        mVexs = new char[vlen];
        for (int i = 0; i < mVexs.length; i++)
            mVexs[i] = vexs[i];
        // 初始化"邊"
        mMatrix = new int[vlen][vlen];
        for (int i = 0; i < vlen; i++)
            for (int j = 0; j < vlen; j++)
                mMatrix[i][j] = matrix[i][j];
        // 統計"邊"
        mEdgNum = 0;
        for (int i = 0; i < vlen; i++)
            for (int j = i+1; j < vlen; j++)
                if (mMatrix[i][j]!=INF)
                    mEdgNum++;
    }

    //返回ch位置
    private int getPosition(char ch) {
        for(int i=0; i<mVexs.length; i++)
            if(mVexs[i]==ch)
                return i;
        return -1;
    }

    // 返回頂點v的第一個鄰接頂點的索引,失敗則返回-1
    private int firstVertex(int v) {
        if (v<0 || v>(mVexs.length-1))
            return -1;
        for (int i = 0; i < mVexs.length; i++)
            if (mMatrix[v][i]!=0 && mMatrix[v][i]!=INF)
                return i;
        return -1;
    }

    //打印鄰接頂點的索引、權重
    private void nextVertexs() {
        for(int v = 0; v < mVexs.length; v++){
            System.out.print("\n結點"+v);
            for (int i = 0; i < mVexs.length; i++) {
               if (mMatrix[v][i]!=0 && mMatrix[v][i]!=INF)
                  System.out.print("鄰接點"+i+"(權重"+mMatrix[v][i]+")");
            }
        } 
    }

    //打印矩陣隊列圖
    public void print() {
        int i, j, sum;
        System.out.printf("結點表:");
        for (i=0; i < mVexs.length; i++) { 
            sum = 0;
            for (j=0; j < mVexs.length; j++) { 
               if (mMatrix[i][j]!=0 && mMatrix[i][j]!=INF) {
                  sum++;
               }
            }
            System.out.print("\n結點"+i+", 標識"+mVexs[i]+", 共有鄰接點"+sum);
        }

        System.out.print("\n\n");
        System.out.printf("鄰接矩陣:\n");
        for (i = 0; i < mVexs.length; i++) {
            for (j = 0; j < mVexs.length; j++)
                System.out.printf("%3d ", mMatrix[i][j]);
            System.out.printf("\n");
        }

        nextVertexs();
    }

    /* * 克魯斯卡爾(Kruskal)最小生成樹 */
    public void kruskal() {
        int index = 0;                      // rets數組的索引
        int[] vends = new int[mEdgNum];     // 用於保存"已有最小生成樹"中每個頂點在該最小樹中的終點。
        EData[] rets = new EData[mEdgNum];  // 結果數組,保存kruskal最小生成樹的邊
        EData[] edges;                      // 圖對應的所有邊

        edges = getEdges(); // 獲取"圖中所有的邊"
        sortEdges(edges, mEdgNum); // 將邊按照"權"的大小進行排序(從小到大)

        for (int i=0; i<mEdgNum; i++) {
            int p1 = getPosition(edges[i].start);      // 獲取第i條邊的"起點"的序號
            int p2 = getPosition(edges[i].end);        // 獲取第i條邊的"終點"的序號

            int m = getEnd(vends, p1);                 // 獲取p1在"已有的最小生成樹"中的終點
            int n = getEnd(vends, p2);                 // 獲取p2在"已有的最小生成樹"中的終點
            // 如果m!=n,意味着"邊i"與"已經添加到最小生成樹中的頂點"沒有形成環路
            if (m != n) {
                vends[m] = n;                       // 設置m在"已有的最小生成樹"中的終點為n
                rets[index++] = edges[i];           // 保存結果
            }
        }

        // 統計並打印"kruskal最小生成樹"的信息
        int length = 0;
        for (int i = 0; i < index; i++)
            length += rets[i].weight;

        System.out.printf("\nKruskal=%d: ", length);
        for (int i = 0; i < index; i++)
            System.out.printf("(%c,%c) ", rets[i].start, rets[i].end);
        System.out.printf("\n");
    }

    //獲取圖中的邊
    private EData[] getEdges() {
        int index=0;
        EData[] edges;
        edges = new EData[mEdgNum];
        for (int i=0; i < mVexs.length; i++) {
            for (int j=i+1; j < mVexs.length; j++) {
                if (mMatrix[i][j]!=INF) {
                    edges[index++] = new EData(mVexs[i], mVexs[j], mMatrix[i][j]);
                }
            }
        }
        return edges;
    }

    //對邊按照權值大小進行排序(由小到大)
    private void sortEdges(EData[] edges, int elen) {
        for (int i=0; i<elen; i++) {
            for (int j=i+1; j<elen; j++) {
                if (edges[i].weight > edges[j].weight) {// 交換"邊i"和"邊j"
                    EData tmp = edges[i];
                    edges[i] = edges[j];
                    edges[j] = tmp;
                }
            }
        }
    }

    //獲取i的終點
    private int getEnd(int[] vends, int i) {
        while (vends[i] != 0)
            i = vends[i];
        return i;
    }

    // 邊的結構體
    private static class EData {
        char start; // 邊的起點
        char end;   // 邊的終點
        int weight; // 邊的權重
        public EData(char start, char end, int weight) {
            this.start = start;
            this.end = end;
            this.weight = weight;
        }
    };


    public static void main(String[] args) {
        char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        int matrix[][] = {
                 /*A*//*B*//*C*//*D*//*E*//*F*//*G*/
          /*A*/ {   0,  12, INF, INF, INF,  16,  14},
          /*B*/ {  12,   0,  10, INF, INF,   7, INF},
          /*C*/ { INF,  10,   0,   3,   5,   6, INF},
          /*D*/ { INF, INF,   3,   0,   4, INF, INF},
          /*E*/ { INF, INF,   5,   4,   0,   2,   8},
          /*F*/ {  16,   7,   6, INF,   2,   0,   9},
          /*G*/ {  14, INF, INF, INF,   8,   9,   0}};
        Kruskal pG;
        pG = new Kruskal(vexs, matrix);

        pG.print();   // 輸出圖的有關信息

        pG.kruskal();   // Kruskal算法生成最小生成樹
    }
}

程序輸出:

結點表:
結點0, 標識A, 共有鄰接點3
結點1, 標識B, 共有鄰接點3
結點2, 標識C, 共有鄰接點4
結點3, 標識D, 共有鄰接點2
結點4, 標識E, 共有鄰接點4
結點5, 標識F, 共有鄰接點5
結點6, 標識G, 共有鄰接點3

鄰接矩陣:
0 12 100 100 100 16 14
12 0 10 100 100 7 100
100 10 0 3 5 6 100
100 100 3 0 4 100 100
100 100 5 4 0 2 8
16 7 6 100 2 0 9
14 100 100 100 8 9 0

結點0鄰接點1(權重12)鄰接點5(權重16)鄰接點6(權重14)
結點1鄰接點0(權重12)鄰接點2(權重10)鄰接點5(權重7)
結點2鄰接點1(權重10)鄰接點3(權重3)鄰接點4(權重5)鄰接點5(權重6)
結點3鄰接點2(權重3)鄰接點4(權重4)
結點4鄰接點2(權重5)鄰接點3(權重4)鄰接點5(權重2)鄰接點6(權重8)
結點5鄰接點0(權重16)鄰接點1(權重7)鄰接點2(權重6)鄰接點4(權重2)鄰接點6(權重9)
結點6鄰接點0(權重14)鄰接點4(權重8)鄰接點5(權重9)

Kruskal=36: (E,F) (C,D) (D,E) (B,F) (E,G) (A,B)


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com