美文网首页
图的连通法之普里姆算法和卡鲁斯卡尔算法

图的连通法之普里姆算法和卡鲁斯卡尔算法

作者: 海重山青 | 来源:发表于2018-03-20 17:19 被阅读0次

    最小生成树

    • 连通图:图的连通其实就是,图的最小连通图其实就是最小生成树
    • :如果一个无向连通图中不存在回路,则这种图称为树。
    • 生成树:无向连通图G的一个子图如果是一颗包含G的所有顶点的树,则该子图称为G的生成树。
    • 最小生成树:或者称为最小代价树,对无向连通图的生成树,各边的权值总和称为生成树的权,权最小的生成树称为最小生成树。
    最小生成树.png 最小生成树2.png
    • 一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边。我们把构造连通网的最小代价生成树。称为最小生成树。
    • 找连通网的最小生成树,经典的有两种算法,普里姆算法克鲁斯卡尔算法

    普里姆算法

    • 普里姆算法:
    #代表无限大
    
    
    假设以v0为基准开始,探测v0到各个顶点的距离:
    v0
    0, 10, #, #, #, 11, #, #, #
    看到v0到v1的距离最短为10.
    
    接下来我们要把v1加到基准里。以v0和v1为基准,探测到各个顶点的距离:
    v0,v1
    0, 0, 18, #, #, 11, 16, #, 12
    看到到v8的举例最短为12.
    这个过程前会把v0到各个顶点的距离和v1到各个顶点的距离作比较,小的留下。
    =======>
    
    v0
    0, 10, #, #, #, 11, #, #, #
    v1
    10, 0, 18, #, #, #, 16, #, 12
    可以看到相同位置的元素,18比#小,11比16小,12比#小。替换后:
    
    v0,v1
    0, 0, 18, #, #, 11, 16, #, 12
    
    =======>
    
    就是这样不断寻找基准距离最短的顶点,将其加入基准。然后再以基准探测周围举例最短的点。一直到所有顶点都找完!
    
    图的深度优先遍历.png
    package com.cx.graphdemo;
    
    public class Graph {
    
        private int vertexSize; // 顶点数量
        private int[] vertexs; // 顶点数组
        private int[][] matrix; // 邻接矩阵
        private static final int MAX_WEIGHT = 1000;
        private boolean[] isVisited; // 是否被访问过
    
        public Graph(int vertexSize) {
            this.vertexSize = vertexSize;
            vertexs = new int[vertexSize];
            matrix = new int[vertexSize][vertexSize];
    
            for (int i = 0; i < vertexSize; i++) {
                vertexs[i] = i;
            }
    
            isVisited = new boolean[vertexSize];
        }
    
        /**
         * prim 普里姆算法
         */
        public void prim() {
            // 最小代价顶点权值的数组,为0表示已经获取最小权值
            int[] lowcost = new int[vertexSize];
            // 放顶点权值
            int[] adjvex = new int[vertexSize];
            int min, minId, sum = 0;
    
            // 把v0数组赋值给lowcost
            for (int i = 1; i < vertexSize; i++) {
                lowcost[i] = matrix[0][i];
            }
    
            // 只是单纯的循环,除此之外没有任何用处
            for (int i = 1; i < vertexSize; i++) {
                min = MAX_WEIGHT;
                minId = 0;
    
                // lowcost数组已更新,所以还要找出lowcost中最小的元素及下标
                for (int j = 1; j < vertexSize; j++) {
                    if (lowcost[j] < min && lowcost[j] > 0) {
                        min = lowcost[j];
                        minId = j;
                    }
                }
    
                /**
                 * 为什么要找到最小元素和下标?<br>
                 * 
                 * 因为最小元素代表当前的已知顶点到其它顶点的最短路径,我们要得到这个<br>
                 * 最短路径通向的顶点。然后周而复始。<br>
                 * 
                 * 核心思想:<br>
                 * 
                 * 从某一顶点开始,找到该顶点周围的最短路径及此路径通向的顶点。<br>
                 * 以这两个顶点为准,找到这两个顶点周围的最短路径及此路径通向的顶点。<br>
                 * ......最终会通过这个"最短路径算法"走完所有的顶点。
                 */
    
                // System.out.println("顶点:" + adjvex[minId] + "权值:" + min);
                sum += min; // 加权重
                lowcost[minId] = 0;
    
                // 在v[minId]中找到比lowcost[]中同等位置小的值
                for (int j = 1; j < vertexSize; j++) {
                    if (lowcost[j] != 0 && matrix[minId][j] < lowcost[j]) {
                        lowcost[j] = matrix[minId][j];
                        adjvex[j] = minId;
                    }
                }
            }
            System.out.println("最小生成树权值和:" + sum);
        }
    
        public static void main(String[] args) {
            Graph graph = new Graph(9);
    
            int[] a1 = new int[] { 0, 10, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 11, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT };
            int[] a2 = new int[] { 10, 0, 18, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 16, MAX_WEIGHT, 12 };
            int[] a3 = new int[] { MAX_WEIGHT, MAX_WEIGHT, 0, 22, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 8 };
            int[] a4 = new int[] { MAX_WEIGHT, MAX_WEIGHT, 22, 0, 20, MAX_WEIGHT, MAX_WEIGHT, 16, 21 };
            int[] a5 = new int[] { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 20, 0, 26, MAX_WEIGHT, 7, MAX_WEIGHT };
            int[] a6 = new int[] { 11, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 26, 0, 17, MAX_WEIGHT, MAX_WEIGHT };
            int[] a7 = new int[] { MAX_WEIGHT, 16, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 17, 0, 19, MAX_WEIGHT };
            int[] a8 = new int[] { MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 16, 7, MAX_WEIGHT, 19, 0, MAX_WEIGHT };
            int[] a9 = new int[] { MAX_WEIGHT, 12, 8, 21, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 0 };
    
            graph.matrix[0] = a1;
            graph.matrix[1] = a2;
            graph.matrix[2] = a3;
            graph.matrix[3] = a4;
            graph.matrix[4] = a5;
            graph.matrix[5] = a6;
            graph.matrix[6] = a7;
            graph.matrix[7] = a8;
            graph.matrix[8] = a9;
    
            graph.prim();
        }
    }
    

    克鲁斯卡尔算法

    克鲁斯卡尔算法.png
    普里姆算法是按顶点来连通图,而克鲁斯卡尔算法则是按边来构成图。
    
    两个顶点确立一条边,没有问题!所有的边可以从小到大排序,也没有问题!
    克鲁斯卡尔算法就是按照这个顺序排好的边来连通图。
    
    规则:
    1.找到最小的边。
    2.探测边的两个顶点是否会构成回环。
    3.如果构成回环则放弃这条边,寻找下一条。
    4.如果不构成回环,则记录。再寻找下一条。
    
    整个寻找过程,可以看下图:一点点画的,一定能看懂!
    
    注意:这里面有一个点。探测是否会构成回环!克鲁斯卡尔用一个神奇的数组来完成这个探索!
    比如:
    v4-v7,权重为7,是最小的边。则记录4号元素为7。也就是说:以起始点为下标,以结束点为值!
    
    [0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    
    v2-v8,权重为8
    [0, 0, 8, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    
    如果这时候第3条为:
    v2-v1,权重为5。这不就冲突了吗?因为2号位置上已经有元素了。如果发生这种情况,就以该位置上值为位置放置新顶点。也就是:
    [0, 0, 8, 0, 7, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
    
    为什么这样做?因为这是最简单的测试回环的方法。
    2号位置是8,代表v2和v8相连。8号位置是1,代表v8和v1相连。
    如果再有边是v1-v2,那么就构成了回环,一个三角回环。所以理解这个数组很重要!
    
    克鲁斯卡尔算法过程.png
    package com.cx.graphtraversal;
    
    public class GraphKruskal {
    
        private Edge[] edges;
        private int edgeSize; // 边的数量
    
        public GraphKruskal(int edgeSize) {
            this.edgeSize = edgeSize;
            edges = new Edge[edgeSize];
        }
    
        public void miniSpanTreeKruskal() {
            int m, n, sum = 0;
            int[] parent = new int[edgeSize]; // 神奇的数组,下标为起点,值为终点
            for (int i = 0; i < edgeSize; i++) {
                parent[i] = 0;
            }
            for (int i = 0; i < edgeSize; i++) {
                n = find(parent, edges[i].begin);
                m = find(parent, edges[i].end);
                if (n != m) { // 代表没有回环
                    parent[n] = m;
                    System.out.println("起始顶点:" + edges[i].begin + "---结束顶点:" + edges[i].end + "~权值:" + edges[i].weight);
                    sum += edges[i].weight;
                } else {
                    System.out.println("第" + i + "条边回环了");
                }
            }
            System.out.println("sum:" + sum);
        }
    
        /**
         * 将神奇数组进行查询获取非回环的值
         */
        public int find(int[] parent, int f) {
            while (parent[f] > 0) {
                System.out.println("找到起点" + f);
                f = parent[f];
                System.out.println("找到终点:" + f);
            }
            return f;
        }
    
        public void createEdgeArray() {
            Edge edge0 = new Edge(4, 7, 7);
            Edge edge1 = new Edge(2, 8, 8);
            Edge edge2 = new Edge(0, 1, 10);
            Edge edge3 = new Edge(0, 5, 11);
            Edge edge4 = new Edge(1, 8, 12);
            Edge edge5 = new Edge(3, 7, 16);
            Edge edge6 = new Edge(1, 6, 16);
            Edge edge7 = new Edge(5, 6, 17);
            Edge edge8 = new Edge(1, 2, 18);
            Edge edge9 = new Edge(6, 7, 19);
            Edge edge10 = new Edge(3, 4, 20);
            Edge edge11 = new Edge(3, 8, 21);
            Edge edge12 = new Edge(2, 3, 22);
            Edge edge13 = new Edge(3, 6, 24);
            Edge edge14 = new Edge(4, 5, 26);
            edges[0] = edge0;
            edges[1] = edge1;
            edges[2] = edge2;
            edges[3] = edge3;
            edges[4] = edge4;
            edges[5] = edge5;
            edges[6] = edge6;
            edges[7] = edge7;
            edges[8] = edge8;
            edges[9] = edge9;
            edges[10] = edge10;
            edges[11] = edge11;
            edges[12] = edge12;
            edges[13] = edge13;
            edges[14] = edge14;
        }
    
        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;
            }
        }
    
        public static void main(String[] args) {
            GraphKruskal graphKruskal = new GraphKruskal(15);
            graphKruskal.createEdgeArray();
            graphKruskal.miniSpanTreeKruskal();
        }
    }
    

    相关文章

      网友评论

          本文标题:图的连通法之普里姆算法和卡鲁斯卡尔算法

          本文链接:https://www.haomeiwen.com/subject/smvbqftx.html