最小生成樹(shù)-Kruskal算法(Java實(shí)現(xiàn))

概念
  • 圖的生成樹(shù)是圖的子圖戏锹,并且不形成環(huán)路
  • 最小生成樹(shù)是帶權(quán)圖中所有生成樹(shù)里邊權(quán)值總和最小的一個(gè)解,可能不唯一
算法思路
  1. 將圖的所有邊按權(quán)重排序
  2. 按順序取出每條邊
  3. 按原位置拼接,如果形成環(huán)就跳過(guò)這一條邊的拼接

算法實(shí)現(xiàn)

圖的實(shí)現(xiàn)
  • 此實(shí)現(xiàn)方法沒(méi)有節(jié)點(diǎn)類(lèi)
  • 采用鄰接矩陣和頂點(diǎn)索引
  • 邊類(lèi)有兩個(gè)成員變量,用于記錄兩個(gè)端點(diǎn)的索引int Aint B
  • 鄰接矩陣int[][] matrix(鄰接矩陣無(wú)需設(shè)置為沿對(duì)角線(xiàn)對(duì)稱(chēng))
    • matrix[i][j]表示從索引i的節(jié)點(diǎn)指向索引j的節(jié)點(diǎn)的權(quán)值
    • 權(quán)值為0表示兩點(diǎn)不連接或者自身與自身不連接
public class Graph<T> {
    private int N; // N個(gè)節(jié)點(diǎn)
    public int[][] matrix;  // 鄰接矩陣
    private T[] datas;  // 保存每個(gè)節(jié)點(diǎn)的數(shù)據(jù)
    public List<Edge> edges = new ArrayList<>();
    class Edge {
        int A;  // 頂點(diǎn)索引
        int B;  // 頂點(diǎn)索引

        public Edge(int a, int b) {
            A = a;
            B = b;
        }

        @Override
        public String toString() {
            return "<" +
                    datas[A] +
                    "-" + matrix[A][B] + "-" + datas[B] +
                    '>';
        }
    }
}
兩個(gè)重點(diǎn)
  • 每條邊按權(quán)重排序
    • edges是圖所有邊的集合
    • 需要重寫(xiě)compare()方法窗市,默認(rèn)是升序排序
    • o1o2是兩個(gè)對(duì)象也即兩條邊,return matrix[o1.A][o1.B] - matrix[o2.A][o2.B]的作用是饮笛,集合調(diào)用sort()方法進(jìn)行排序時(shí)咨察,按前一條邊的權(quán)重減去后一條邊的權(quán)重,小于0(前一條邊的權(quán)重小)則兩條邊的位置不變福青,大于0則交換位置(大概意思是這樣)
        edges.sort(new Comparator<Edge>() {
            @Override
            public int compare(Edge o1, Edge o2) {
                return matrix[o1.A][o1.B] - matrix[o2.A][o2.B];
            }
        });
  • 判斷是否有環(huán)(回路)
    • 基本思路:判斷一條邊加入的時(shí)候兩個(gè)端點(diǎn)的 "終點(diǎn)" 是否相同扎拣,相同則說(shuō)明有環(huán)
    • getEnd()
      • int[] ends保存所有節(jié)點(diǎn)的終點(diǎn)索引,但不是一開(kāi)始就確定,而是執(zhí)行最小生成樹(shù)算法的過(guò)程中才動(dòng)態(tài)確定每個(gè)元素的一個(gè)數(shù)組
      • 最開(kāi)始ends所有元素都是0
      • 如果傳進(jìn)來(lái)的索引i二蓝,ends[i] == 0則說(shuō)明這個(gè)節(jié)點(diǎn)是第一次被訪(fǎng)問(wèn)到誉券,則直接返回自身i(因?yàn)椴粫?huì)進(jìn)入循環(huán)),表示此節(jié)點(diǎn)的終點(diǎn)是自己
      • 傳進(jìn)來(lái)索引i刊愚,ends[i] != 0則說(shuō)明這個(gè)節(jié)點(diǎn)不是第一次被訪(fǎng)問(wèn)到踊跟,則把這個(gè)節(jié)點(diǎn)的終點(diǎn)索引賦值給i,如果ends[i]仍然不為0則說(shuō)明最開(kāi)始索引i的終點(diǎn)也有終點(diǎn)鸥诽,則再把終點(diǎn)的終點(diǎn)索引賦值給i商玫,while (ends[i] != 0)的作用就是不斷往下找到真正的終點(diǎn)
    • 在遍歷邊集合過(guò)程中,endOfA != endOfB如果邊的兩個(gè)端點(diǎn)的終點(diǎn)索引不同牡借,ends[endOfA] = endOfB;則把第一個(gè)端點(diǎn)的終點(diǎn)的終點(diǎn)設(shè)置為第二個(gè)端點(diǎn)的終點(diǎn)(可能有點(diǎn)繞拳昌,這步驟的原因是兩個(gè)端點(diǎn)終點(diǎn)不同,所以可以加入最終結(jié)果的邊集合钠龙,也就是這條邊已經(jīng)確定加入圖中炬藤,所以第一個(gè)端點(diǎn)必定能按這條邊到達(dá)第二個(gè)端點(diǎn)的終點(diǎn))
    /**
     * 本方法獲取索引為i的頂點(diǎn)的終點(diǎn), 用于判斷兩個(gè)頂點(diǎn)的終點(diǎn)是否相同
     *
     * @param ends 記錄各個(gè)頂點(diǎn)的終點(diǎn)(遍歷過(guò)程中才動(dòng)態(tài)確定的數(shù)組)
     * @param i    傳入的頂點(diǎn)索引
     * @return int 傳入索引對(duì)應(yīng)頂點(diǎn)的終點(diǎn)索引
     */
    private int getEnd(int[] ends, int i) {
        while (ends[i] != 0) {  // 循環(huán)是為了找到最終的終點(diǎn)
            i = ends[i];
        }
        return i;
    }

    /*****下面的代碼在最小生成樹(shù)算法主體方法中***********************/
            // 如果這條邊取出來(lái)拼接后構(gòu)成環(huán), 則取消拼接操作
            int endOfA = getEnd(ends, edge.A);
            int endOfB = getEnd(ends, edge.B);
            // 如果邊的第一個(gè)頂點(diǎn)的終點(diǎn)不等于第二個(gè)頂點(diǎn)的終點(diǎn)
            if (endOfA != endOfB) {
                // 設(shè)置第一個(gè)頂點(diǎn)的終點(diǎn)的終點(diǎn)為第二個(gè)頂點(diǎn)的終點(diǎn)
                ends[endOfA] = endOfB;
                result.add(edge);  // 最小生成樹(shù)結(jié)果的邊集合
            }
算法主體方法
  1. int[] ends保存取出各個(gè)邊后依次拼接時(shí), 各個(gè)頂點(diǎn)的終點(diǎn)索引
  2. 把邊的權(quán)重排序
  3. List<Edge> result保存最終結(jié)果的所有邊的集合
  4. 依次取出,不形成回路則拼接
  5. 將結(jié)果集合的所有邊以鄰接矩陣int[][] treeMatrix的形式表現(xiàn)
    /**
     * 克魯斯卡爾算法-最小生成樹(shù)
     *
     * @return void
     */
    public void KruskalTree() {
        // ends保存取出各個(gè)邊后依次拼接時(shí), 各個(gè)頂點(diǎn)的終點(diǎn)索引
        int[] ends = new int[N];
        // 把邊的權(quán)重排序
        edges.sort(new Comparator<Edge>() {
            @Override
            public int compare(Edge o1, Edge o2) {
                return matrix[o1.A][o1.B] - matrix[o2.A][o2.B];
            }
        });
        // 保存最終結(jié)果的所有邊的集合
        List<Edge> result = new ArrayList<>();
        // 依次取出并拼接
        for (Edge edge : edges) {
            // 如果這條邊取出來(lái)拼接后構(gòu)成環(huán), 則取消拼接操作
            int endOfA = getEnd(ends, edge.A);
            int endOfB = getEnd(ends, edge.B);
            // 如果邊的第一個(gè)頂點(diǎn)的終點(diǎn)不等于第二個(gè)頂點(diǎn)的終點(diǎn)
            if (endOfA != endOfB) {
                // 設(shè)置第一個(gè)頂點(diǎn)的終點(diǎn)的終點(diǎn)為第二個(gè)頂點(diǎn)的終點(diǎn)
                ends[endOfA] = endOfB;
                result.add(edge);
            }
        }
        // 查看一下結(jié)果
        System.out.println(result);
        // 返回最小生成樹(shù)的鄰接矩陣
        int[][] treeMatrix = new int[N][N];
        // 將結(jié)果集合的所有邊以鄰接矩陣的形式表現(xiàn)
        for (Edge edge : result) {
            treeMatrix[edge.A][edge.B] = matrix[edge.A][edge.B];
        }
        System.out.println("最小生成樹(shù)的鄰接矩陣: ");
        for (int[] nums : treeMatrix) {
            System.out.println(Arrays.toString(nums));
        }
    }
測(cè)試
  1. 6個(gè)節(jié)點(diǎn)碴里,對(duì)應(yīng)保存數(shù)據(jù)為字母ABCDEF
  2. int[][] set是為了初始化鄰接矩陣graph.setMatrix(set[i][0], set[i][1], set[i][2])
  3. 設(shè)置邊集合graph.setEdges()
  4. 執(zhí)行算法graph.KruskalTree()
    public static void main(String[] args) {
        Graph<String> graph = new Graph<>(6);
        graph.setDatas(new String[]{"A", "B", "C", "D", "E", "F"});
        // {端點(diǎn)索引, 端點(diǎn)索引, 權(quán)值}
        int[][] set = {{0, 1, 1},
                {0, 2, 3},
                {1, 3, 2},
                {1, 5, 2},
                {2, 3, 4},
                {2, 5, 7},
                {3, 4, 1},
                {4, 5, 8}};

        for (int i = 0; i < set.length; i++) {
            // graph.setMatrix(端點(diǎn)索引, 端點(diǎn)索引, 權(quán)值)
            graph.setMatrix(set[i][0], set[i][1], set[i][2]);
        }
        graph.setEdges();
        graph.KruskalTree();
    }
測(cè)試結(jié)果
最小生成樹(shù)

輸出結(jié)果如下
[<A-1-B>, <D-1-E>, <B-2-D>, <B-2-F>, <A-3-C>]
最小生成樹(shù)的鄰接矩陣:
[0, 1, 3, 0, 0, 0]
[0, 0, 0, 2, 0, 2]
[0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 0]
[0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0]

完整代碼實(shí)現(xiàn)

public class Graph<T> {
    private int N; // 節(jié)點(diǎn)個(gè)數(shù)
    public int[][] matrix;  // 鄰接矩陣
    private T[] datas;  // 保存每個(gè)節(jié)點(diǎn)的數(shù)據(jù)
    public List<Edge> edges = new ArrayList<>();  // 邊集合

    class Edge {
        int A;  // 頂點(diǎn)索引
        int B;  // 頂點(diǎn)索引

        public Edge(int a, int b) {
            A = a;
            B = b;
        }

        // 重寫(xiě)toString()方法方便查看結(jié)果
        @Override
        public String toString() {
            return "<" +
                    datas[A] +
                    "-" + matrix[A][B] + "-" + datas[B] +
                    '>';
        }
    }

    public Graph(int N) {
        this.N = N;
        matrix = new int[N][N];
        statuses = new Status[N];
        datas = (T[]) new Object[N];  // 泛型數(shù)組實(shí)例化
    }

    /**
     * 鄰接矩陣保存的信息是從一個(gè)節(jié)點(diǎn)指向另一個(gè)節(jié)點(diǎn)的信息
     *
     * @param from   從這個(gè)節(jié)點(diǎn)
     * @param to     指向這個(gè)節(jié)點(diǎn)
     * @param weight 路徑權(quán)重
     * @return void
     */
    public void setMatrix(int from, int to, int weight) {
        matrix[from][to] = weight;
    }

    /**
     * 設(shè)置圖的邊(matrix初始化之后才調(diào)用)
     *
     * @return void
     */
    public void setEdges() {
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                if (matrix[i][j] > 0) {
                    edges.add(new Edge(i, j));
                }
            }
        }
    }

    /**
     * 最小生成樹(shù)中判斷是否有回路的重要方法
     * 獲取索引為i的頂點(diǎn)的終點(diǎn), 用于判斷兩個(gè)頂點(diǎn)的終點(diǎn)是否相同
     *
     * @param ends 記錄各個(gè)頂點(diǎn)的終點(diǎn)(遍歷過(guò)程中才動(dòng)態(tài)確定的數(shù)組)
     * @param i    傳入的頂點(diǎn)索引
     * @return int 原頂點(diǎn)的終點(diǎn)索引
     */
    private int getEnd(int[] ends, int i) {
        System.out.print(datas[i] + "->");
        while (ends[i] != 0) {  // 艸我懂了: 循環(huán)是為了找到最終的終點(diǎn)
            i = ends[i];
        }
        System.out.println(datas[i]);

        return i;
    }

    /**
     * 克魯斯卡爾算法-最小生成樹(shù)
     *
     * @return void
     */
    public void KruskalTree() {
        // ends保存取出各個(gè)邊后依次拼接時(shí), 各個(gè)頂點(diǎn)的終點(diǎn)索引
        int[] ends = new int[N];
        // 把邊的權(quán)重排序
        edges.sort(new Comparator<Edge>() {
            @Override
            public int compare(Edge o1, Edge o2) {
                return matrix[o1.A][o1.B] - matrix[o2.A][o2.B];
            }
        });
        // 保存最小生成樹(shù)中包含的邊集合
        List<Edge> result = new ArrayList<>();
        // 依次取出并拼接
        for (Edge edge : edges) {
            // 如果這條邊取出來(lái)拼接后構(gòu)成環(huán), 則取消拼接操作
            int endOfA = getEnd(ends, edge.A);
            int endOfB = getEnd(ends, edge.B);
            // 如果邊的第一個(gè)頂點(diǎn)的終點(diǎn)不等于第二個(gè)頂點(diǎn)的終點(diǎn)
            if (endOfA != endOfB) {
                // 設(shè)置第一個(gè)頂點(diǎn)的終點(diǎn)的終點(diǎn)為第二個(gè)頂點(diǎn)的終點(diǎn)
                ends[endOfA] = endOfB;
                result.add(edge);
            }
        }
        System.out.println(result);
        // 返回最小生成樹(shù)的鄰接矩陣
        int[][] treeMatrix = new int[N][N];
        for (Edge edge : result) {
            treeMatrix[edge.A][edge.B] = matrix[edge.A][edge.B];
        }
        System.out.println("最小生成樹(shù)的鄰接矩陣: ");
        for (int[] nums : treeMatrix) {
            System.out.println(Arrays.toString(nums));
        }
    }
    public static void main(String[] args) {
        Graph<String> graph = new Graph<>(6);
        graph.setDatas(new String[]{"A", "B", "C", "D", "E", "F"});
        // {端點(diǎn)索引, 端點(diǎn)索引, 權(quán)值}
        int[][] set = {{0, 1, 1},
                {0, 2, 3},
                {1, 3, 2},
                {1, 5, 2},
                {2, 3, 4},
                {2, 5, 7},
                {3, 4, 1},
                {4, 5, 8}};

        for (int i = 0; i < set.length; i++) {
            // graph.setMatrix(端點(diǎn)索引, 端點(diǎn)索引, 權(quán)值)
            graph.setMatrix(set[i][0], set[i][1], set[i][2]);
        }
        graph.setEdges();
        graph.KruskalTree();
    }
}

謝謝沈矿,第一次寫(xiě)文,不喜輕噴咬腋,狗頭保命

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末羹膳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子根竿,更是在濱河造成了極大的恐慌陵像,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寇壳,死亡現(xiàn)場(chǎng)離奇詭異蠢壹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)九巡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蹂季,“玉大人冕广,你說(shuō)我怎么就攤上這事〕ソ啵” “怎么了撒汉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)涕滋。 經(jīng)常有香客問(wèn)我睬辐,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任溯饵,我火速辦了婚禮侵俗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘丰刊。我一直安慰自己隘谣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布啄巧。 她就那樣靜靜地躺著寻歧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪秩仆。 梳的紋絲不亂的頭發(fā)上码泛,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音澄耍,去河邊找鬼噪珊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛逾苫,可吹牛的內(nèi)容都是我干的卿城。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼铅搓,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼瑟押!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起星掰,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤多望,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后氢烘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體怀偷,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年播玖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了椎工。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜀踏,死狀恐怖维蒙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情果覆,我是刑警寧澤颅痊,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站局待,受9級(jí)特大地震影響斑响,放射性物質(zhì)發(fā)生泄漏菱属。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一舰罚、第九天 我趴在偏房一處隱蔽的房頂上張望纽门。 院中可真熱鬧,春花似錦沸停、人聲如沸膜毁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瘟滨。三九已至,卻和暖如春能颁,著一層夾襖步出監(jiān)牢的瞬間杂瘸,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工伙菊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留败玉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓镜硕,卻偏偏與公主長(zhǎng)得像运翼,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子兴枯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353