算法其實很簡單—克魯斯卡爾算法

目錄

1. 克魯斯卡爾算法介紹

2. 公交站問題

2.1 克魯斯卡爾算法圖解

2.2 克魯斯卡爾算法分析

2.3 如何判斷是否構成迴路

3. 代碼實現


1. 克魯斯卡爾算法介紹

1)克魯斯卡爾(Kruskal)算法,是用來求加權連通圖的最小生成樹的算法。(最小生成樹也可以通過普里姆算法生成,具體可參考算法其實很簡單—普利姆算法

2)基本思想:按照權值從小到大的順序選擇n-1條邊,並保證這n-1條邊不構成迴路

3)具體做法:首先構造一一個只含n個頂點的森林,然後依權值從小到大從連通網中選擇邊加入到森林中,並使森林中不產生迴路,直至森林變成一棵樹爲止

 

2. 公交站問題

 

1)某城市新增7個站點(A,B,C,D,E,F,G), 現在需要修路把7個站點連通

2)各個站點的距離用邊線表示(權),比如A-B距離12公里

3) 問:如何修路保證各個站點都能連通,並且總的修建公路總里程最短?

2.1 克魯斯卡爾算法圖解

第1步:將邊<E,F>加入R中。

邊<E,F>的權值最小,因此將它加入到最小生成樹結果R中。

 

第2步:將邊<C,D>加入R中。

上一步操作之後,邊<C,D>的權值最小,因此將它加入到最小生成樹結果R中。

 

第3步:將邊<D,E>加入R中。

上一 步操作之後,邊<D,E>的權值最小,因此將它加入到最小生成樹結果R中。

 

第4步:將邊<B,F>加入R中。

上一步操作之後,邊<C,E>的權值最小,但<C,E>會和已有的邊構成迴路;因此,跳過邊<C,E>。同理,跳過邊<C,F>.將邊<B,F>加入到最小生成樹結果R中。

 

第5步:將邊<E,G>加入R中。

上一步操作之後,邊<E,G>的權值最小,因此將它加入到最小生成樹結果R中。

 

第6步:將邊<A,B>加入R中。

上一步操作之後,邊<F,G>的權值最小,但<F,G>會和已有的邊構成迴路;因此,跳過邊<F,G>。同理,跳過邊<B,C>.將邊<A,B>加入到最小生成樹結果R中。

 

此時,最小生成樹構造完成!它包括的邊依次是: < <E,F> <C,D> <D,E><B,F> < <E,G> <A,B>.

 

2.2 克魯斯卡爾算法分析

根據前面介紹的克魯斯卡爾算法的基本思想和做法,我們能夠了解到,克魯斯卡爾算法重點需要解決的以下兩個問題:

問題對圖的所有邊按照權值大小進行排序。

問題二將邊添加到最小生成樹中時,怎麼樣判斷是否形成了迴路。

問題一很好解決,採用排序算法進行排序即可。

問題二,處理方式是:記錄頂點在"最小生成樹"中的終點,頂點的終點是"在最小生成樹中與它連通的最大頂點"。然後每次需要將一條邊添加到最小生存樹時,判斷該邊的兩個頂點的終點是否重合,重合的話則會構成迴路。

2.3 如何判斷是否構成迴路

 

在將<E,F> <C,D> <D,E>加入到最小生成樹R中之後,這幾條邊的頂點就都有了終點:

(01) C的終點是F。

(02) D的終點是F。

(03)E的終點是F。

(04) F的終點是F。

 

關於終點的說明:

1)就是將所有頂點按照從小到大的順序排列好之後;某個頂點的終點就是"與它連通的最大頂點"。

2)因此,接下來,雖然<C,E>是權值最小的邊。但是C和E的終點都是F,即它們的終點相同,因此,將<C,E>加入最小生成樹的話,會形成迴路。這就是判斷迴路的方式。也就是說,我們加入的邊的兩個頂點不能都指向同一個終點,否則將構成迴路。

3. 代碼實現

package com.example.datastructureandalgorithm.kruska;

import java.util.Arrays;
import java.util.Collections;

/**
 * @author 浪子傑
 * @version 1.0
 * @date 2020/6/17
 */
public class KruskaDemo {

    public static final int INF = Integer.MAX_VALUE;

    public static void main(String[] args) {
        char[] vertexs = {'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}
        };
        Kruska kruska = new Kruska(vertexs, matrix);
        kruska.print();
        System.out.println(Arrays.toString(kruska.getEdges()));
        EData[] eDatas = kruska.getEdges();
        Collections.sort(Arrays.asList(eDatas));
//        kruska.sortEData(eDatas);
        System.out.println(Arrays.toString(eDatas));
        kruska.kruska();
    }
}


class Kruska {
    private int edgeNum;
    private char[] vertexs;
    private int[][] matrix;

    public Kruska(char[] vertexs, int[][] matrix) {
        this.vertexs = vertexs;
        this.matrix = matrix;
        int length = vertexs.length;
        for (int i = 0; i < length; i++) {
            for (int j = i + 1; j < length; j++) {
                if (matrix[i][j] != KruskaDemo.INF) {
                    edgeNum++;
                }
            }
        }
    }

    public void print() {
        for (int i = 0; i < vertexs.length; i++) {
            for (int j = 0; j < vertexs.length; j++) {
                System.out.printf("%12d", matrix[i][j]);
            }
            System.out.println();
        }
    }

    /**
     * 使用冒泡排序對eDatas進行排序
     *
     * @param eDatas
     */
    public void sortEData(EData[] eDatas) {
        for (int i = 0; i < eDatas.length - 1; i++) {
            for (int j = 0; j < eDatas.length - i - 1; j++) {
                if (eDatas[j + 1].weight < eDatas[j].weight) {
                    EData temp = eDatas[j];
                    eDatas[j] = eDatas[j + 1];
                    eDatas[j + 1] = temp;
                }
            }
        }
    }

    /**
     * 查找ch對應點的下標
     *
     * @param ch
     * @return
     */
    public int getPosition(char ch) {
        for (int i = 0; i < vertexs.length; i++) {
            if (vertexs[i] == ch) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 獲取鄰接矩陣轉爲的EData
     *
     * @return
     */
    public EData[] getEdges() {
        int index = 0;
        EData[] eDatas = new EData[edgeNum];
        for (int i = 0; i < vertexs.length; i++) {
            for (int j = i + 1; j < vertexs.length; j++) {
                if (matrix[i][j] != KruskaDemo.INF) {
                    eDatas[index++] = new EData(vertexs[i], vertexs[j], matrix[i][j]);
                }
            }
        }
        return eDatas;
    }

    /**
     * 獲取下標爲i的頂點的終點,用於判斷兩個頂點的重點是否相同
     *
     * @param ends 記錄各個頂點的重點,動態生成的
     * @param i
     * @return
     */
    public int getEnd(int[] ends, int i) {
        while (ends[i] != 0) {
            i = ends[i];
        }
        return i;
    }

    public void kruska() {
        // 表示結果數組的索引
        int index = 0;
        // 記錄每個頂點的重點
        int[] ends = new int[edgeNum];
        // 最終返回的結果
        EData[] result = new EData[edgeNum];
        // 獲取所有邊的集合
        EData[] eDatas = getEdges();
        // 對eData進行排序
        sortEData(eDatas);
        for (int i = 0; i < edgeNum; i++) {
            // 獲取開始頂點的位置
            int p1 = getPosition(eDatas[i].start);
            // 獲取結束頂點的位置
            int p2 = getPosition(eDatas[i].end);
            // 獲取終點位置
            int m = getEnd(ends, p1);
            int n = getEnd(ends, p2);
            // 當不相同時,說明沒有形成回來
            if (m != n) {
                ends[m] = n;
                result[index++] = eDatas[i];
            }
        }

        for (int i = 0; i < index; i++) {
            System.out.println(result[i]);
        }
    }
}

class EData implements Comparable<EData> {
    char start;
    char end;
    int weight;

    public EData(char start, char end, int weight) {
        this.start = start;
        this.end = end;
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "EData{" +
                "<" + start +
                ", " + end +
                "> =" + weight +
                '}';
    }

    @Override
    public int compareTo(EData o) {
        return this.weight - o.weight;
    }
}

 

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