克鲁斯卡尔算法(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("邻接矩阵为:\n");
        for(int i=0;i<vertexs.length;i++)
        {
            for(int j=0;j<vertexs.length;j++)
            {
                System.out.printf("%12d\t",matrix[i][j]);
            }
            System.out.printf("\n");
        }
    }
    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;
    }
    
}

 

posted @ 2020-03-16 21:09  计算机的探索者  阅读(430)  评论(0编辑  收藏  举报