最小生成树-克鲁斯卡尔算法(kruskal)

package org.kruskal;

/**
 * 克鲁斯卡尔算法
 * 解决公交站点问题
 * 假设有7个公交站,现在要给这7个公交站联通
 * 问题:要求如何设计路线,使得这7个公交站互相链接,且修路总距离最短
 *
 * Union-Find set)这个数据结构可以方便快速的解决这个问题。
 * 基本的处理思想是:初始时把每个对象看作是一个单元素集合;然后依次按顺序读入联通边,
 * 将连通边中的两个元素合并。在此过程中将重复使用一个搜索(Find)运算,
 * 确定一个集合在那个集合中。当读入一个连通边(u,v)时,
 * 先判断u和v是否在同一个集合中,如果是则不用合并;如果不是,
 * 则用一个合并(Union)运算把u、v所在集合合并,使得这两个集合中的任意两个元素都连通。
 * 因此并查集在处理时,主要用到搜索和合并两个运算。
 * 为了方便并查集的描述与实现,通常把先后加入到一个集合中的元素表示成一个树结构,
 * 并用根结点的序号来表示这个集合。因此定义一个parent[n]的数组,parent[i]中存放的就是结点i所在的树中结点i的父亲节点的序号。
 * 例如,如果parent[4]=5,就是说4号结点的父亲结点是5号结点。约定:如果i的父结点(即parent[i])是负数,
 * 则表示结点i就是它所在的集合的根结点,因为集合中没有结点的序号是负的;并且用负数的绝对值作为这个集合中所含结点的个数。
 * 例如,如果parent[7]=-4,说明7号结点就是它所在集合的根结点,
 * 这个集合有四个元素。初始时结点的parent值为-1(每个结点都是根结点,只包含它自己一个元素)。
 * @author cjj_1
 * @date 2020-09-09 11:15
 */
public class KruskalCase {
    
    
    char[] vertexs = {
    
    
            'A','B','C','D','E','F','G'
    };
    int INF = Integer.MAX_VALUE;
    //连线图
    int[][] matrix = {
    
    
            {
    
    INF,12,INF,INF,INF,16,14},
            {
    
    12,INF,10,INF,INF,7,INF},
            {
    
    INF,10,INF,3,5,6,INF},
            {
    
    INF,INF,3,INF,4,INF,INF},
            {
    
    INF,INF,5,4,INF,2,8},
            {
    
    16,7,6,INF,2,INF,9},
            {
    
    14,INF,INF,INF,8,9,INF}
    };
    int[] parent = new int[vertexs.length];

    public static void main(String[] args) {
    
    
        KruskalCase kruskalCase = new KruskalCase();
        Edata[] edges = kruskalCase.getEdatas(kruskalCase.matrix);
        kruskalCase.sort(edges);
        kruskalCase.kruskal(edges);
    }

    /**
     * 初始化parent的值
     * @param parent
     */
    public void ufSet(int[] parent){
    
    
        for (int i=0;i<parent.length;i++){
    
    
            parent[i] = -1;
        }
    }

    /**
     * 克鲁斯卡尔算法
     */
    public void kruskal(Edata[] edges){
    
    
        int[] end = new int[edges.length];
        Edata[] effectEdges = new Edata[edges.length];
        int index =0;
        char p1;
        char p2;
        int p1_num;
        int p2_num;
        int end1;
        int end2;
        ufSet(parent);
        for (int i = 0;i<edges.length;i++){
    
    
            p1 = edges[i].start;
            p2 = edges[i].end;
            p1_num = getPosition(vertexs,p1);
            p2_num = getPosition(vertexs,p2);
            if(find(p1_num) != find(p2_num)){
    
    
                union(p1_num,p2_num);
                effectEdges[index++] = edges[i];
            }
        }
        for (int i =0;i<effectEdges.length;i++){
    
    
            System.out.println(effectEdges[i]!=null?effectEdges[i].toString():"");
        }
    }


    /**
     * 两个集合并时,任一方可做为另一方的子孙。怎样来处理呢,现在一般采用加权合并,
     * 把两个集合中元素个数少的根结点做为元素个数多的根结点的子女结点。
     * 这样处理有什么优势呢?直观上看,可以减少树中的深层元素的个数,减少后续查找时间。
     * @param R1
     * @param R2
     */
   public   void union( int R1, int R2 ) {
    
    
       int r1 = find(R1), r2 = find(R2); //r1 为 R1 的根结点,r2 为 R2 的根结点
       int tmp = parent[r1] + parent[r2]; //两个集合结点个数之和(负数)
       //如果 R2 所在树结点个数 > R1 所在树结点个数(注意 parent[r1]是负数)
       if (parent[r1] > parent[r2]) //优化方案――加权法则
       {
    
    
           parent[r1] = r2;
           parent[r2] = tmp;
       } else {
    
    
           parent[r2] = r1;
           parent[r1] = tmp;
       }
   }


        /**
         * 对边权值升序排序
         * @param edges
         */
    public void sort(Edata[] edges){
    
    
        Edata temp ;
        for(int i=0;i<edges.length;i++){
    
    
            for (int j = 1 ;j<edges.length;j++){
    
    
                if(edges[j-1].weight>edges[j].weight){
    
    
                    temp = edges[j-1];
                    edges[j-1]=edges[j];
                    edges[j]= temp;
                }
            }
        }
    }

    /**
     * 获取一个字符的位置
     * @param vertexs
     * @param c
     * @return
     */
    public int getPosition(char[] vertexs,char c){
    
    
        for (int i=0;i<vertexs.length;i++){
    
    
            if(c==vertexs[i])
                return i;
        }
        return -1;
    }
    public Edata[] getEdatas(int[][] matrix){
    
    
        int edegesNum = 0;
        for (int i=0;i<matrix.length;i++){
    
    
            for (int j=i+1;j<matrix[i].length;j++){
    
    
                if(matrix[i][j] != INF)
                    edegesNum++;
            }
        }
        Edata[] edges = new Edata[edegesNum];
        int index =0;
        for (int i=0;i<matrix.length;i++){
    
    
            for (int j=i+1;j<matrix[i].length;j++){
    
    
                if(matrix[i][j] != INF){
    
    
                    Edata edge = new Edata(vertexs[i],vertexs[j],matrix[i][j]);
                    edges[index++] = edge;
                }
            }
        }
        return edges;
    }


    /**
     * 在 Find 函数中如果仅仅靠一个循环来直接得到结点所属集合的根结点的话,
     * 通过多次的 Union 操作就会有很多结点在树的比较深层次中,再查找起来就会很费时。
     * 可以通过压缩路径来加快后续的查找速度:增加一个 While 循环,
     * 每次都把从结点 x 到集合根结点的路径上经过的结点直接设置为根结点的子女结点。虽然这增加了时间,但以后的查找会更快。
     * 如图 3.4 所示,假设从结点 x = 6 开始压缩路径,
     * 则从结点 6 到根结点1 的路径上有 3 个结点:6、10、8,压缩后,这 3 个结点都直接成为根结点的子女结点
     * 找到r1的跟节点
     * @param r1
     * @return
     */
    private int find(int r1) {
    
    
            int x = r1;
           while (this.parent[x]>=0){
    
    
               x = parent[x];
           }
           if(x!=r1){
    
    
               parent[r1] = x;
           }
           return x;
        }

}

    /**
 * 一个边对象
 */
class Edata{
    
    
    char start;
    char end;
    int weight;
    public Edata(){
    
    

    }
    public Edata(char start,char end,int weight){
    
    
        this.start =start;
        this.end = end;
        this.weight = weight;
    }
    @Override
    public String toString() {
    
    
        return "Edata{" +
                "<" + start +
                ", " + end +
                "> =" + weight +
                '}';
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_40128696/article/details/108510772