克鲁斯卡尔(Kruskal)算法

修路问题

1、本质:最小生成树问题

2、最小生成树(MST),Minimum Cost Spanning Tree,给定一个带权的无向连通图,选取一棵生成树,使树上所有边上权的总和为最小

(1) N个顶点,一定有N-1条边

(2) 包含全部顶点

(3) N-1条边都在图中

 

克鲁斯卡尔算法查找最小生成树的方法

1、步骤

(1)将连通网中所有的边按照权值大小做升序排序,从权值最小的边开始选择,只要此边不和已选择的边一起构成环路,就可以选择它组成最小生成树

(2)对于 N 个顶点的连通网,挑选出 N-1 条符合条件的边,这些边组成的生成树就是最小生成树

2、Kruskal 算法从 n 个顶点 n 条边出发,不断扩充边,直到包括 n - 1 条边为止,适用于求边稀疏的最小生成树

3、举例

(1)连通网

(2)将连通网中的所有边按照权值大小做升序排序

(3)从 B-D 边开始挑选,由于尚未选择任何边组成最小生成树,且 B-D 自身不会构成环路,所以 B-D 边可以组成最小生成树

(4)D-T 边不会和已选 B-D 边构成环路,可以组成最小生成树

(5)A-C 边不会和已选 B-D、D-T 边构成环路,可以组成最小生成树

(6)C-D 边不会和已选 A-C、B-D、D-T 边构成环路,可以组成最小生成树

(7)C-B 边会和已选 C-D、B-D 边构成环路,因此不能组成最小生成树

(8)B-T 、A-B、S-A 三条边都会和已选 A-C、C-D、B-D、D-T 构成环路,都不能组成最小生成树。而 S-A 不会和已选边构成环路,可以组成最小生成树

(9)如图所示,对于一个包含 6 个顶点的连通网,已经选择了 5 条边,这些边组成的生成树就是最小生成树

 

克鲁斯卡尔(Kruskal)算法求最小生成树

1、基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路

2、步骤

(1)构造一个只含n个顶点的森林

(2)依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止

3、判断是否形成回路

(1)记录顶点在"最小生成树"中的终点

(2)将所有顶点按照从小到大的顺序排列,某个顶点的终点就是“在最小生成树中与它连通的最大顶点”

4、Kruskal 算法的计算一幅含有 V 个顶点和 E 条边的连通加权无向图的最小生成树

(1)所需的空间和 E 成正比

(2)所需的时间和 ElogE 成正比(最坏情况)

 

代码实现

public class Kruskal {
    public static final int BLOCK = Integer.MAX_VALUE;//表示顶点之间不直接连通,顶点自身不连通

    //Kruskal算法解决最小生成树问题
    public static void main(String[] args) {
        char[] vertexes = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        int[][] matrix = {
                {BLOCK, 12, BLOCK, BLOCK, BLOCK, 16, 14},
                {12, BLOCK, 10, BLOCK, BLOCK, 7, BLOCK},
                {BLOCK, 10, BLOCK, 3, 5, 6, BLOCK},
                {BLOCK, BLOCK, 3, BLOCK, 4, BLOCK, BLOCK},
                {BLOCK, BLOCK, 5, 4, BLOCK, 2, 8},
                {16, 7, 6, BLOCK, 2, BLOCK, 9},
                {14, BLOCK, BLOCK, BLOCK, 8, 9, BLOCK}
        };
        Graph graph = new Graph(vertexes, matrix);
        MinimumCostSpanningTree minimumCostSpanningTree = new MinimumCostSpanningTree();
        minimumCostSpanningTree.kruskal(graph);
    }
}

class MinimumCostSpanningTree {

    //克鲁斯卡尔算法创建最小生成树
    public void kruskal(Graph graph) {
        int select = 0;//记录最小生成树的边的个数
        int[] ends = new int[graph.numOfEdges];
        Edge[] result = new Edge[graph.numOfEdges];//最小生成树
        Edge[] edges = graph.getEdges();
        graph.sortEdges(edges);//按照边的权值大小,从小到大排序
        for (int i = 0; i < graph.numOfEdges; i++) {
            //下标为i的边的两个顶点
            int vertex1 = graph.getIndex(edges[i].start);
            int vertex2 = graph.getIndex(edges[i].end);
            int end1 = graph.getEnd(ends, vertex1);//vertex1的终点
            int end2 = graph.getEnd(ends, vertex2);//vertex2的终点
            if (end1 != end2) {//终点不同,不构成回路
                ends[end1] = end2;
                result[select++] = edges[i];
            }
        }
        System.out.println("最小生成树");
        for (int i = 0; i < select; i++) {
            System.out.println(result[i]);
        }
    }
}


//带权无向图
class Graph {
    public int numOfEdges;//边的个数
    public char[] vertexes;//顶点数组
    public int[][] matrix;//邻接矩阵,一维数组的元素代表边的权值

    //创建图,为方便而直接赋值data,matrix,但拷贝data,matrix可以不破坏原数据
    public Graph(char[] vertexes, int[][] matrix) {
        this.vertexes = vertexes;
        this.matrix = matrix;
        for (int i = 0; i < vertexes.length; i++) {
            for (int j = i + 1; j < vertexes.length; j++) {
                if (this.matrix[i][j] != Kruskal.BLOCK) {
                    numOfEdges++;
                }
            }
        }
    }

    //根据边的权值,从小到大冒泡排序
    public void sortEdges(Edge[] edges) {
        Edge temp;
        for (int i = 0; i < edges.length - 1; i++) {
            for (int j = 0; j < edges.length - 1 - i; j++) {
                if (edges[j].weight > edges[j + 1].weight) {//½»»»
                    temp = edges[j];
                    edges[j] = edges[j + 1];
                    edges[j + 1] = temp;
                }
            }
        }
    }

    //获取顶点的下标
    public int getIndex(char vertex) {
        for (int i = 0; i < vertexes.length; i++) {
            if (vertexes[i] == vertex) {
                return i;
            }
        }
        return -1;
    }

    //获取图中的所有边,及边两边顶点
    public Edge[] getEdges() {
        int index = 0;
        Edge[] edges = new Edge[numOfEdges];
        for (int i = 0; i < vertexes.length; i++) {
            for (int j = i + 1; j < vertexes.length; j++) {
                if (matrix[i][j] != Kruskal.BLOCK) {
                    edges[index++] = new Edge(vertexes[i], vertexes[j], matrix[i][j]);
                }
            }
        }
        return edges;
    }

    //获取下标为i的顶点的终点
    //ends:记录各顶点对应终点下标,该数组在生成最小生成树有动态变化
    public int getEnd(int[] ends, int i) {
        //从i自身所在的最小生成树,向根方向找终点
        while (ends[i] != 0) {
            i = ends[i];
        }
        return i;
    }
}

//边
class Edge {
    char start;//起点
    char end;//终点
    int weight;//权值

    //构造器
    public Edge(char start, char end, int weight) {
        this.start = start;
        this.end = end;
        this.weight = weight;
    }

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

 

posted @ 2022-02-27 22:28  半条咸鱼  阅读(853)  评论(0编辑  收藏  举报