Kruskal算法解析及Java代码实现(附图)

最小生成树

       在含有n个顶点的连通图中选择n-1条边,构成一棵极小连通子图,并使该连通子图中n-1条边上权值之和达到最小,则称其为连通网的最小生成树。

example:

例如,对于如上图G4所示的连通网可以有多棵权值总和不相同的生成树。

 

克鲁斯卡尔(Kruskal)算法

克鲁斯卡尔(Kruskal)算法,是用来求加权连通图的最小生成树的算法,基于连续的按照最小边的策略进行生成树的构建。Kruskal算法开始的时候把每个顶点都当做棵树,这样就是个森林,然后选取最小的边进行树的连接,最后生成只含有一棵树。

基本思想:将图的存储结构使用边集数组的形式表示,并将边集数组按权值从小到大排序,遍历边集数组,每次选取一条边并判断是否构成环路,不会构成环路则将其加入最小生成树,最终只会包含n-1条边(n为无向图的顶点数)。


具体做法

  1. 每次选取最小的边来构成生成树
  2. 将这条边加入后不能形成闭环

 

伪代码:

图解:

第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>

 

package kruskal;

import java.util.ArrayList;
import java.util.List;

public class Kruskal {
	 
    /** 邻接矩阵, 为二维数组, 也称地图 */
    private int[][] matrix;
    /** 设置最大权重范围, 表示正无穷 */
    private int MAX_WEIGHT = Integer.MAX_VALUE;
    /** 边集数组, 采用List接口的实现类ArrayList(数组线性表), 遍历元素和随机访问元素的效率比较高 */
    private List<Edge> edgeList = new ArrayList<Edge>();
 
    /**
     * 创建地图
     */
    private void createGraph(int index) {  //index表示顶点个数
        matrix = new int[index][index];    //邻接矩阵是一个长宽相等的正方形矩阵
        //邻接矩阵中每一行的数组, 按照原图以此填充
        int[] v0 = { 0, 10, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 11, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT };
        int[] v1 = { 10, 0, 18, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 16, MAX_WEIGHT, 12 };
        int[] v2 = { MAX_WEIGHT, 18, 0, 22, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 8 };
        int[] v3 = { MAX_WEIGHT, MAX_WEIGHT, 22, 0, 20, MAX_WEIGHT, MAX_WEIGHT, 16, 21 };
        int[] v4 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 20, 0, 26, MAX_WEIGHT, 7, MAX_WEIGHT };
        int[] v5 = { 11, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 26, 0, 17, MAX_WEIGHT, MAX_WEIGHT };
        int[] v6 = { MAX_WEIGHT, 16, MAX_WEIGHT, 24, MAX_WEIGHT, 17, 0, 19, MAX_WEIGHT };
        int[] v7 = { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 16, 7, MAX_WEIGHT, 19, 0, MAX_WEIGHT };
        int[] v8 = { MAX_WEIGHT, 12, 8, 21, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 0 };
        matrix[0] = v0;
        matrix[1] = v1;
        matrix[2] = v2;
        matrix[3] = v3;
        matrix[4] = v4;
        matrix[5] = v5;
        matrix[6] = v6;
        matrix[7] = v7;
        matrix[8] = v8;
    }
    
    /**
     * 创建边集数组,并且对他们按权值从小到大排序(顺序存储结构也可以认为是数组吧)
     */
    public void createEdges() {
    	//调用带参构造,此处三个参数分别为begin,end,weight
        Edge v0 = new Edge(4, 7, 7);
        Edge v1 = new Edge(2, 8, 8);
        Edge v2 = new Edge(0, 1, 10);
        Edge v3 = new Edge(0, 5, 11);
        Edge v4 = new Edge(1, 8, 12);
        Edge v5 = new Edge(3, 7, 16);
        Edge v6 = new Edge(1, 6, 16);
        Edge v7 = new Edge(5, 6, 17);
        Edge v8 = new Edge(1, 2, 18);
        Edge v9 = new Edge(6, 7, 19);
        Edge v10 = new Edge(3, 4, 20);
        Edge v11 = new Edge(3, 8, 21);
        Edge v12 = new Edge(2, 3, 22);
        Edge v13 = new Edge(3, 6, 24);
        Edge v14 = new Edge(4, 5, 26);
        
        //同时加入边集数组中(数组线性表)
        edgeList.add(v0);
        edgeList.add(v1);
        edgeList.add(v2);
        edgeList.add(v3);
        edgeList.add(v4);
        edgeList.add(v5);
        edgeList.add(v6);
        edgeList.add(v7);
        edgeList.add(v8);
        edgeList.add(v9);
        edgeList.add(v10);
        edgeList.add(v11);
        edgeList.add(v12);
        edgeList.add(v13);
        edgeList.add(v14);
    }
 
    /**
     * 克鲁斯卡尔算法
     */
    public void kruskal() {
        //创建图和边集数组, 该图有v0-v8共9个顶点s
        createGraph(9);
        //可以由图转出边集数组并按权从小到大排序,这里为了方便观察直接写出来了
        createEdges();
        
        //定义一个数组用来判断边与边是否形成环路(并查集知识)
        int[] parent = new int[9];
        
        /** 权值总和, 是判断最小生成树的重要依据 , 初始化为0 */
        int sum = 0;
        
        int n, m;
        
        //遍历得到每一条边
        for (int i = 0; i < edgeList.size(); i++) {
            Edge edge= edgeList.get(i);
            n = find(parent, edge.getBegin());  
            m = find(parent, edge.getEnd());    
            
            //如果 m == n 说明形成了环路或者两个结点都在一棵树上
            //反之,如果 n和m 不等则说明没有闭合线路
            if (n != m) {
                parent[n] = m;   // 设置n在"已有的最小生成树"中的终点为m(就是把n合并在m所在的连通分量中)
                System.out.println("(" + edge.getBegin() + "," + edge.getEnd() + ")" +edge.getWeight());
                
                sum += edge.getWeight();  //加上对应边的权重,更新sum值
            }
        }
        
        System.out.println("权值总和为:" + sum);
    }
 
    public int find(int[] parent, int index) {
        while (parent[index] > 0) {
        	//parent[0] = 1表示顶点0,1已经加入生成树中
            index = parent[index];
        }
        return index;
    }
 
    public static void main(String[] args) {
        Kruskal graph = new Kruskal();
        graph.kruskal();
    }
 
}
package kruskal;

/**
 * 克鲁斯卡尔->最小生成树边实体
 * @author Administrator
 *
 */
class Edge {
 
    private int begin;   //边的起点
    private int end;     //边的终点
    private int weight;  //边的权重
 
    public Edge(int begin, int end, int weight) {  //带参构造
        super();
        this.begin = begin;
        this.end = end;
        this.weight = weight;
    }
 
    public int getBegin() {
        return begin;
    }
 
    public void setBegin(int begin) {
        this.begin = begin;
    }
 
    public int getEnd() {
        return end;
    }
 
    public void setEnd(int end) {
        this.end = end;
    }
 
    public int getWeight() {
        return weight;
    }
 
    public void setWeight(int weight) {
        this.weight = weight;
    }
 
    @Override
    public String toString() {
        return "Edge [begin=" + begin + ", end=" + end + ", weight=" + weight + "]";
    }
 
}

代码中所用的图数据如下:

运行结果:

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值