克魯斯卡爾(Kruskal)算法之加權連通圖的最小生成樹問題

1.圖的幾個概念

(1)連通圖:在無向圖中,若任意兩個頂點vi與vj都有路徑相通,則稱該無向圖爲連通圖
(2)強連通圖:在有向圖中,若任意兩個頂點vi與vj都有路徑相通,則稱該有向圖爲強連通圖
(3)連通網:在連通圖中,若圖的邊具有一定的意義,每一條邊都對應着一個數值,稱爲,權代表着連接兩個頂點的代價,稱這種連通圖叫做連通網
(4)生成樹:一個連通圖的生成樹是指一個連通子圖,它含有圖中全部 n 個頂點,但只有足以構成一棵樹的n-1條邊。一棵有n個頂點的生成樹有且僅有 n-1 條邊,如果生成樹中再添加一條邊,則必定成環
(5)最小生成樹:在連通網的所有生成樹中,所有邊的代價和最小的生成樹,稱爲最小生成樹(Minimum Cost Spanning Tree),簡稱 MST

è¿éåå¾çæè¿°

2.並查集的基本介紹

需要詳情,自行百度

// 初始化的模板
int[] pre = new int[n];
for(int i = 0; i < n; i++) {
   pre[i] = i;
}

// 查詢的模板(含路徑壓縮)
int find(int x){
   if(pre[x] == x) {
	   return x;
   }
   // 遞歸
   return pre[x] = find(pre[x]);
}

// 合併的模板
void merge(int x, int y){
   int fx = find(x),
   int fy = find(y);
   if(fx != fy) {
       pre[fx] = fy;
   }
}

3.克魯斯卡爾(Kruskal)算法的概述

用於求解圖的最小生成樹
貪心策略:每次都選擇權值最小的邊作爲最小生成樹的邊

4.克魯斯卡爾(Kruskal)算法的基本思路

(1)構造只有 n 個(圖 graph 的頂點集合的大小)頂點的森林,即構造一個數組結構的並查集,初始化爲各個頂點的終點爲頂點自身
(2)把圖 graph 中頂點連通的邊按照權值從小到大進行排序
(3)按邊的權值從小到大取出來,加入到森林中,並使森林中不產生迴路,直至森林變成一棵樹爲止
(4)此時最小生成樹有 n 個頂點,n-1 條邊,並且 n-1 條邊不構成迴路
加入邊需要注意的事項
a.判斷待加入的邊 edge ,加入到當前子圖後是否會構成迴路,如果構成迴路,則取出下一條權值較小的邊繼續判斷,
b.如果不構成迴路,則加入當前邊 edge 到當前子圖中,逐漸構成最小生成樹
c.構成迴路的判斷標準:當前待加入的邊所對應的兩個頂點在當前子圖中的終點是否相同
d.終點的理解:在當前子圖中,與待加入的頂點連通的最後頂點(並查集的查詢操作)
比如:已知 A->B 連通(A 的終點爲 B),B->C 連通(B 的終點爲 C),則通過 並查集 可知 A 的終點爲 C(A->C 連通) (可理解爲終點的傳遞性)

注意:在使用普里姆算法(Prim)構造最小生成樹的過程中,最小生成樹必定不會構成迴路,因爲頂點 ui 是從已經被訪問過的頂點集合 U 中獲取到的頂點,頂點 vj 是從未被訪問過的頂點集合 V-U(差集) 中獲取到的頂點,(ui,vj)構成的邊加入到最小生成樹中必定不會構成迴路,但是使用 克魯斯卡爾算法(Kruskal) 構造最小生成樹時,添加邊到最小生成樹中,存在構成迴路的可能,因此要判斷加入的邊是否會構成迴路,使用 並查集 判斷是否會構成迴路

5.克魯斯卡爾(Kruskal)算法的代碼實現

package com.zzb.algorithm.kruskal;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;

/**
 * @Auther: Administrator
 * @Date: 2020/3/31 22:08
 * @Description: 克魯斯卡爾算法:求加權連通圖的最小生成樹的算法
 * 貪心策略:每次都選擇權值最小的邊作爲最小生成樹的邊
 */
public class Kruskal {
    public static void main(String[] args) {
        // 頂點值數組
        String[] vertexArray = {"A", "B", "C", "D", "E", "F", "G"};
        // 鄰接矩陣
        final int N = Integer.MAX_VALUE/2; // 表示不可以連接
        int[][] matrix = new int[vertexArray.length][vertexArray.length];
        matrix[0]=new int[]{N,5,7,N,N,N,2};
        matrix[1]=new int[]{5,N,N,9,N,N,3};
        matrix[2]=new int[]{7,N,N,N,8,N,N};
        matrix[3]=new int[]{N,9,N,N,N,4,N};
        matrix[4]=new int[]{N,N,8,N,N,5,4};
        matrix[5]=new int[]{N,N,N,4,5,N,6};
        matrix[6]=new int[]{2,3,N,N,4,6,N};
        // 創建圖對象
        Graph graph = new Graph(vertexArray, matrix);
        // 構造最小生成樹
        Edge[] edgeArrayOfMST = graph.createMST();
        // 查看詳情
        int sum = 0;
        for(int i = 0; i < edgeArrayOfMST.length; i++) {
            System.out.println(edgeArrayOfMST[i]);
            sum += edgeArrayOfMST[i].getWeight();
        }
        System.out.println("總權值 == " + sum);
        /*
        邊 <A,G> 權值 2
        邊 <B,G> 權值 3
        邊 <D,F> 權值 4
        邊 <E,G> 權值 4
        邊 <E,F> 權值 5
        邊 <A,C> 權值 7
        總權值 == 25*/
    }
}

/**
 * 圖類
 */
class Graph implements Serializable {
    private static final long serialVersionUID = 7611879468252933629L;

    // 存儲圖中各個頂點的集合
    private String[] vertexArray;
    // 存儲圖中各條邊的鄰接矩陣
    private int[][] edgeArray;
    // 記錄圖中各個頂點的終點,用於判斷是否構成迴路
    // 數組下標代表當前頂點在 vertexArray 中的下標(對應),數組的值代表當前頂點的終點的下標(並查集的使用)
    private int[] endPointArray;

    /**
     * 構造器初始化
     *
     * @param vertexArray 頂點
     * @param edgeArray 連接矩陣
     */
    public Graph(String[] vertexArray, int[][] edgeArray) {
        this.vertexArray = vertexArray;
        this.edgeArray = edgeArray;
        // 記錄圖中各個頂點的終點,用於判斷是否構成迴路
        // 數組下標代表當前頂點在 vertexArray 中的下標(對應),數組的值代表當前頂點的終點的下標(並查集的使用)
        // 初始化各個頂點的終點爲自身(並查集的初始化)
        endPointArray = new int[this.vertexArray.length];
        for(int i = 0; i < endPointArray.length; i++) {
            endPointArray[i] = i;
        }
    }

    /**
     * 構造最小生成樹
     *
     * @return 以邊的數組方式保存最小生成樹
     */
    public Edge[] createMST() {
        // 克魯斯卡爾算法
        Edge[] edgeArrayOfMST = kruskal(this);
        return edgeArrayOfMST;
    }

    /**
     * 克魯斯卡爾算法
     * 貪心策略:每次都選擇權值最小的邊作爲最小生成樹的邊
     *
     * @param graph 由哪個圖來構造最小生成樹
     * @return 以邊的數組方式保存最小生成樹
     */
    private Edge[] kruskal(Graph graph) {
        // 以邊的數組方式保存最小生成樹
        int index = 0;
        Edge[] edgeArrayOfMST = new Edge[graph.getVertexArray().length - 1];

        // graph 的兩點之間連通的邊封裝成 Edge 對象
        // graph 的鄰接矩陣的邊取值要麼是具體的權值,要麼就是代表不連通的值 Integer.MAX_VALUE/2
        ArrayList<Edge> edgeList = new ArrayList<>();
        for(int i = 0; i < graph.getEdgeArray().length; i++) {
            for(int j = i+1; j < graph.getEdgeArray()[0].length; j++) {
                if(graph.getEdgeArray()[i][j] != Integer.MAX_VALUE/2) {
                    edgeList.add(new Edge(graph.vertexArray[i], graph.vertexArray[j], graph.getEdgeArray()[i][j]));
                }
            }
        }

        // 按邊的權值從小到大排序
        edgeList.sort(new Comparator<Edge>() {
            @Override
            public int compare(Edge o1, Edge o2) {
                if(o1.getWeight() < o2.getWeight()) {
                    return -1;
                }else if(o1.getWeight() > o2.getWeight()) {
                    return 1;
                }else {
                    return 0;
                }
            }
        });

        int startIndex; // 一條邊的開始點的索引
        int endIndex; // 一條邊的結束點的索引
        int endPointOfStartIndex; // 一條邊的開始點的終點
        int endPointOfEndIndex; // 一條邊的結束點的終點
        // 按邊的權值從小到大取出來,加入到最小生成樹中
        for(Edge edge : edgeList) {
            // 判斷待加入的邊 edge ,加入到當前子圖後是否會構成迴路,如果構成迴路,則取出下一條權值較小的邊繼續判斷,
            // 如果不構成迴路,則加入當前邊 edge 到當前子圖中,逐漸構成最小生成樹
            // 構成迴路的判斷標準:當前待加入的邊所對應的兩個頂點在當前子圖中的終點是否相同
            // 終點的理解:在當前子圖中,與待加入的頂點連通的最後頂點(並查集的查詢操作)
            // 比如:A->B 連通(A 的終點爲 B),B->C 連通(B 的終點爲 C),則通過 並查集 可知 A 的終點爲 C (可理解爲終點的傳遞性)
            startIndex = graph.getIndexOfVertex(edge.getStart()); // 頂點
            endIndex = graph.getIndexOfVertex(edge.getEnd()); // 頂點
            endPointOfStartIndex = graph.getIndexOfEndPoint(startIndex); // 終點
            endPointOfEndIndex = graph.getIndexOfEndPoint(endIndex); // 終點
            if(endPointOfStartIndex != endPointOfEndIndex) {
                edgeArrayOfMST[index] = edge;
                index++;
                // 設置 endPointOfStartIndex 的終點爲 endPointOfEndIndex
                graph.merge( endPointOfStartIndex, endPointOfEndIndex);
            }
        }
        // 返回最小生成樹
        return edgeArrayOfMST;
    }

    /**
     * 獲取某個頂點的終點(並查集的查詢操作)
     * 
     * @param index 某個頂點在圖的頂點集合 vertexArray 中的索引
     * @return 某個頂點的終點
     */
    private int getIndexOfEndPoint(int index) {
        if(this.endPointArray[index] == index) {
            return index;
        }
        // 遞歸
        return this.endPointArray[index] = getIndexOfEndPoint(this.endPointArray[index]);
    }

    /**
     * 設置 endPointOfStartIndex 的終點爲 endPointOfEndIndex(並查集的合併操作)
     *
     * @param endPointOfStartIndex 開始點
     * @param endPointOfEndIndex 結束點
     */
    private void merge(int endPointOfStartIndex, int endPointOfEndIndex) {
        this.endPointArray[endPointOfStartIndex] = endPointOfEndIndex;
    }

    /**
     * 獲取各個頂點所對應的索引
     *
     * @param vertex 頂點所在集合的值
     * @return 獲取各個頂點所對應的索引
     */
    private int getIndexOfVertex(String vertex) {
        for(int i = 0; i < this.vertexArray.length; i++) {
            if(vertex.equals(this.vertexArray[i])) {
                return i;
            }
        }
        return -1;
    }

    public String[] getVertexArray() {
        return vertexArray;
    }

    public void setVertexArray(String[] vertexArray) {
        this.vertexArray = vertexArray;
    }

    public int[][] getEdgeArray() {
        return edgeArray;
    }

    public void setEdgeArray(int[][] edgeArray) {
        this.edgeArray = edgeArray;
    }
}

/**
 * 邊類
 */
class Edge implements Serializable {
    private static final long serialVersionUID = 5009546370782229661L;

    // 邊的開始點
    private String start;
    // 邊的結束點
    private String end;
    // 邊的權值
    private int weight;

    /**
     * 構造器初始化
     *
     * @param start 邊的開始點
     * @param end 邊的結束點
     * @param weight 邊的權值
     */
    public Edge(String start, String end, int weight) {
        this.start = start;
        this.end = end;
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "邊 <" + this.getStart() + "," + this.getEnd() + "> " + "權值 " + this.getWeight();
    }

    public String getStart() {
        return start;
    }

    public void setStart(String start) {
        this.start = start;
    }

    public String getEnd() {
        return end;
    }

    public void setEnd(String end) {
        this.end = end;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章