• 克鲁斯卡尔算法(Kruskal算法)与最小生成树问题


    @author:QYX

    开源结束,我回来了!

    基本思想:(1)构造一个只含n个顶点,边集为空的子图。若将图中各个顶点看成一棵树的根节点,则它是一个含有n棵树的森林。(2)从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图。也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之(3)依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。

    简单来说:(1)将图中的所有边都去掉。(2)将边按权值从小到大的顺序添加到图中,保证添加的过程中不会形成环(3)重复上一步直到连接所有顶点,此时就生成了最小生成树。这是一种贪心策略。

    问题:判断某条边<u, v>的加入是否会在已经选定的边集集合中形成环。

    解决方法:使用并查集,分别找出两个顶点u, v所在树的根节点。若根节点相同,说明u, v在同一棵树中,则u, v连接起来会形成环;若根节点不同,则u, v不在一棵树中,连接起来不会形成环,而是将两棵树合并

    package com.qyx.krusal;
    
    import java.util.Arrays;
    
    import javax.swing.plaf.basic.BasicBorders.MarginBorder;
    
    public class KrusalCase {
        private int edgeNum;//边的个数
        private char[] vertexs;//顶点数组
        private int[][] matrix;//邻接矩阵
        private static final int INF=Integer.MAX_VALUE;//使用INF表示两个顶点不能连通
        
        public KrusalCase(char[] vertexs, int[][] matrix) {
    //        super();
    //        this.edgeNum = edgeNum;
    //        this.vertexs = vertexs;
    //        this.matrix = matrix;
            //初始化顶点数和边的个数
            int vlen=vertexs.length;
            this.vertexs=new char[vlen];
            for(int i=0;i<vertexs.length;i++)
            {
                this.vertexs[i]=vertexs[i];
            }
            //初始化边,使用的是复制拷贝的方式
            this.matrix=new int[vlen][vlen];
            for(int i=0;i<vlen;i++)
            {
                for(int j=0;j<vlen;j++)
                {
                    this.matrix[i][j]=matrix[i][j];
                }
            }
            //统计边
            for(int i=0;i<vlen;i++)
            {
                for(int j=i+1;j<vlen;j++)
                {
                    if(this.matrix[i][j]!=INF)
                    {
                        this.edgeNum++;
                    }
                }
            }
        }
        //打印邻接矩阵
        public void print()
        {
            System.out.printf("邻接矩阵为:
    ");
            for(int i=0;i<vertexs.length;i++)
            {
                for(int j=0;j<vertexs.length;j++)
                {
                    System.out.printf("%12d	",matrix[i][j]);
                }
                System.out.printf("
    ");
            }
        }
        public static void main(String[] args) {
            char[] vertexs={'A','B','C','D','E','F','G'};
            //创建克鲁斯卡尔的邻接矩阵
            int matrix[][]={
                    /*A*//*B*//*C*//*D*//*E*//*F*//*G*/
                    {0,12,INF,INF,INF,16,14},    /*A*/
                    {12,0,10,INF,INF,7,INF},    /*B*/
                    {INF,10,0,3,5,6,INF},        /*C*/
                    {INF,INF,3,0,4,INF,INF},    /*D*/
                    {INF,INF,5,4,0,2,8},        /*E*/
                    {16,7,6,INF,2,0,9},            /*F*/
                    {14,INF,INF,INF,8,9,0},        /*G*/
            };
            KrusalCase kcase=new KrusalCase(vertexs, matrix);
            kcase.print();
            kcase.kruskal();
        }
        //对边进行排序,使用冒泡
        /**
         * 功能:使用冒泡排序对边进行排序
         * @param edges 边的集合
         */
        private void sortEdges(EData[] edges)
        {
            for(int i=0;i<edges.length-1;i++)
            {
                for(int j=0;j<edges.length-1-i;j++)
                {
                    
                    //这里可以让边实现Comparable<T>接口
                    if(edges[j].compareTo(edges[j+1])>0)
                    {
                        EData temp=edges[j];
                        edges[j]=edges[j+1];
                        edges[j+1]=temp;
                    }
                }
            }
        }
        /**
         * 
         * @param ch 顶点的值,比如'A','B'
         * @return 返回ch顶点对应的下标,如果找不到,返回-1
         */
        private int getPosition(char ch)
        {
            for(int i=0;i<vertexs.length;i++)
            {
                if(vertexs[i]==ch)
                {
                    return i;
                }
            }
            return -1;
        }
        /**
         * 功能获取图中的边,放到EDate[]数组中,后面我们需要遍历该数组
         * @return 返回边的数组
         * 通过matrix邻接矩阵来获取
         */
        private EData[] getEdges()
        {
            int index=0;
            EData[] datas=new EData[edgeNum];
            for(int i=0;i<vertexs.length;i++)
            {
                for(int j=i+1;j<vertexs.length;j++)
                {
                    if(matrix[i][j]!=INF)
                    {
                        //遍历左三角,因为右三角和左三角对称,而且对角线全为0,所以抛弃对角线取一半的不为INF的数据就行
                        datas[index++]=new EData(vertexs[i], vertexs[j], matrix[i][j]);
                    }
                }
            }
            return datas;
        }
        /**
         * 功能:获取下标为i的顶点的终点,用于后面判断两个顶点的终点是否相同
         * @param ends 该数组记录了各个顶点对应的终点是哪个,ends数组是在遍历过程中逐步形成的
         * @param i 表示传入的顶点对应的下标
         * @return 返回的就是下标为i的这个顶点的终点的下标
         */
        private 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];//用于保存"已有最小生成树"中的每个顶点在最小生成树中的终点
            //创建结果数组,保存最后的最小生成树
            EData[] rets=new EData[edgeNum];
            //获取图中所有的边的集合,一共有12条边
            EData[] edges=getEdges();
            //按照边的权值从小到大排序
            sortEdges(edges);
            //遍历edges,将边添加到最小生成树中时,判断是准备加入的边否形成了回路,如果没有,就加入rets,否则不能加入
            for(int i=0;i<edgeNum;i++)
            {
                //获取到第i条边的第一个顶点
                int p1=getPosition(edges[i].getStart());
                //获取到第i条边的第二个顶点
                int p2=getPosition(edges[i].getEnd());
                //获取p1这个顶点在已有的最小生成树中的终点
                int m=getEnd(ends, p1);
                //获取p2这个顶点在已有的最小生成树中的终点
                int n=getEnd(ends, p2);
                //判断是否构成回路
                if(m!=n)
                {
                    ends[m]=n;
                    rets[index++]=edges[i];
                }
            }
            //统计并打印最小生成树,输出rets
            System.out.println("最小生成树为"+Arrays.toString(rets));
        }
    }
    //创建一个类,它的对象实例就表示一条边
    class EData<T> implements Comparable<T>{
        private char start; //边的一个点
        private char end;    //边的另一个店
        private int weight;    //边的权值
        
        public char getStart() {
            return start;
        }
        public void setStart(char start) {
            this.start = start;
        }
        public char getEnd() {
            return end;
        }
        public void setEnd(char end) {
            this.end = end;
        }
        public int getWeight() {
            return weight;
        }
        public void setWeight(int weight) {
            this.weight = weight;
        }
        //构造器
        public EData(char start, char end, int weight) {
            super();
            this.start = start;
            this.end = end;
            this.weight = weight;
        }
        //重写toString方法,便于输出边
        @Override
        public String toString() {
            return "EData [start=" + start + ", end=" + end + ", weight=" + weight + "]";
        }
        @Override
        public int compareTo(T o) {
            
            if(((EData)o).getWeight()>this.getWeight())
            {
                return -1;
            }
            return 1;
        }
        
    }
  • 相关阅读:
    Golang 读写文件的操作
    初始 Elasticsearch
    数据存储单位的换算(bytes、KB MB GB TB PB EB ZB YB DB NB)
    Go 语言类型元信息的通用结构 _type
    空接口
    HashMap和Hashtable的区别
    谈谈final, finally, finalize的区别
    理解AOP
    Struts2 OGNL概述
    Struts2原理
  • 原文地址:https://www.cnblogs.com/qyx66/p/12506775.html
Copyright © 2020-2023  润新知