Java实现之克鲁斯卡尔(Kruskal)算法

一.问题引入

1.问题引入

1)某城市新增7个站点(A,B,C,D,E,F,G),现在需要修路把7个站点连通

2)各个站点的距离用边线表示(权),比如A-B距离12公里

3)问:如何修路保证各个站点都能连通,并且总的修建公路总里程最短?

Java实现之克鲁斯卡尔(Kruskal)算法_第1张图片

 二.克鲁斯卡尔算法

1.基本介绍

1)克鲁斯卡尔(Kruskal)算法,是用来求加权连通图的最小生成树的算法。
2)基本思想:按照权值从小到大的顺序选择n-1条边,并确定这n-1条边不构成回路
3)具体做法:首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网
中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止

2.思路分析

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

Java实现之克鲁斯卡尔(Kruskal)算法_第2张图片

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

Java实现之克鲁斯卡尔(Kruskal)算法_第3张图片

 第一步:

Java实现之克鲁斯卡尔(Kruskal)算法_第4张图片

  第二步:

Java实现之克鲁斯卡尔(Kruskal)算法_第5张图片

 第三步:

Java实现之克鲁斯卡尔(Kruskal)算法_第6张图片

 第四步:

Java实现之克鲁斯卡尔(Kruskal)算法_第7张图片

 第五步:

Java实现之克鲁斯卡尔(Kruskal)算法_第8张图片

 第六步:

Java实现之克鲁斯卡尔(Kruskal)算法_第9张图片

 3.问题分析

根据前面介绍的克鲁斯卡尔算法的基本思想和做法,我们能够了解到,克鲁斯卡尔算法重点需要解决的以下两个问题:
问题一:对图的所有边按照权值大小进行排序。
问题二:将边添加到最小生成树中时,怎么样判断是否形成了回路。问题一很好解决,采用排序算法进行排序即可。
问题二的处理方式是:记录顶点在"最小生成树"中的终点,顶点的终点是"在最小生成树中与它连通的最大顶点"。然后每次需要将一条边添加到最小生存树时,判断该边的两个顶点的终点是否重合,重合的话则会构成回路。

4.判断构成回路图解

Java实现之克鲁斯卡尔(Kruskal)算法_第10张图片

在将加入到最小生成树R中之后,这几条边的顶点就都有了终点:
(01)C的终点是F。

(02)D的终点是F。

(03)E的终点是F。

(04)F的终点是F。

关于终点的说明:
1)就是将所有顶点按照从小到大的顺序排列好之后;某个顶点的终点就是"与它连通的最大顶点"。
2)因此,接下来,虽然是权值最小的边。但是C和E的终点都是F,即
它们的终点相同,因此,将加入最小生成树的话,会形成回路,这就是判断回路的方式.也就是说,我们加入的边的两个顶点不能都指向同一个终点,否则将构成回路。

5.代码实现

public class Kruskal {
    public int edgeNum; //边的个数
    public char[] vertex; //顶点数组
    public int[][] matrix; //

    public static void main(String[] args) {
        char[] vertex = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        int[][] matrix = new int[][]{
                {0, 12, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, 16, 14},
                {12, 0, 10, Integer.MAX_VALUE, Integer.MAX_VALUE, 7, Integer.MAX_VALUE},
                {Integer.MAX_VALUE, 10, 0, 3, 5, 6, Integer.MAX_VALUE},
                {Integer.MAX_VALUE, Integer.MAX_VALUE, 3, 0, 4, Integer.MAX_VALUE, Integer.MAX_VALUE},
                {Integer.MAX_VALUE, Integer.MAX_VALUE, 5, 4, 0, 2, 8},
                {16, 7, 6, Integer.MAX_VALUE, 2, 0, 9},
                {14, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, 8, 9, 0}};
        Kruskal kruskal = new Kruskal(vertex, matrix);
        kruskal.showGraph();
        System.out.println(kruskal.edgeNum);

        EdgeData[] edges = kruskal.getEdges();
        kruskal.sortEdges(edges);
        for (EdgeData edge : edges) {
            System.out.println(edge);
        }
        System.out.println("------------------------");
        kruskal.kruskal();


    }


    public Kruskal(char[] vertex, int[][] matrix) {
        //初始化顶点数和边的个数
        this.vertex = vertex;
        this.matrix = matrix;
        //统计边的条数
        for (int i = 0; i < vertex.length; i++) {
            for (int j = i + 1; j < vertex.length; j++) {
                if (matrix[i][j] != Integer.MAX_VALUE)
                    edgeNum++;
            }
        }
    }

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

    //对边进行排序处理
    public void sortEdges(EdgeData[] edges) {
        Arrays.sort(edges);

    }

    //返回顶点的下标,如'A'-->0,找不到返回-1
    public int getPosition(char c) {
        for (int i = 0; i < vertex.length; i++) {
            if (c == vertex[i]) {
                return i;
            }
        }
        return -1;

    }

    //获取到图中的边,放到EdgeData[]数组中,通过int[][] matrix获取
    public EdgeData[] getEdges() {
        int index = 0;
        EdgeData[] edges = new EdgeData[edgeNum];
        for (int i = 0; i < vertex.length; i++) {
            for (int j = i + 1; j < vertex.length; j++) {
                if (matrix[i][j] != Integer.MAX_VALUE) {
                    edges[index++] = new EdgeData(vertex[i], vertex[j], matrix[i][j]);
                }


            }
        }
        return edges;
    }

    /**
     * 功能:获取下标为i的顶点的终点,用于后面判断两个顶点的终点是否相等
     *
     * @param ends 数组记录了各个顶点的终点,ends数组是在遍历过程中逐渐形成的
     * @param i
     * @return 返回下标为i的终点
     */
    public int getEnd(int[] ends, int i) {
        while (ends[i] != 0) {
            i = ends[i];
        }
        return i;

    }

    //
    public void kruskal() {
        int index = 0;
        int[] ends = new int[edgeNum];//表示已有生成树
        //创建结果数组,保存最小生成树
        ArrayList result = new ArrayList<>();

        //获取图中所有边的集合
        EdgeData[] edges = getEdges();

        //按照权值,从小到大排序
        sortEdges(edges);

        //遍历edges数组,将边添加到最小生成树,并判断是否构成回路,如果没有,就加入
        for (int i = 0; i < edgeNum; i++) {
            //获取第i条边的起点
            int start = getPosition(edges[i].start);
            //获取第i条边的终点
            int end = getPosition(edges[i].end);

            //获取start顶点在最小生成树的终点
            int end1 = getEnd(ends, start);
            //获取end顶点在最小生成树的终点
            int end2 = getEnd(ends, end);
            //判断是否构成回路
            if (end1 != end2) {//没有构成回路
                ends[end1] = end2;
                result.add(edges[i]);//此边加入到数组中

            }
        }
        //输出最小生成树
        System.out.println(result);


    }

}

class EdgeData implements Comparable {
    char start;  //边的起点
    char end;//边的终点
    int weight;  //边的权值

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

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

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

你可能感兴趣的:(java,算法,java,数据结构)