最小生成树Kruskal算法实现(Java)

       Kruskal算法,通过收集边的方式来形成最小生成树,一开始将每个顶点看成一颗树,那么形成最小生成树就是是森林合并为树的一个过程,每次都选择一条权重最小的边,同样也是贪心算法的体现,但是要避免形成环。这里就提出了两个问题:

(1)如何保证每次选择的边的权重都是最小的?

(2)如何判断待收集的边,是否会与已经收集的边形成环?

 

对于问题(1):有两种方法,一种是对边的权重进行从小到大的排序,另外一种则是使用最小堆来实现(效率更好)

针对问题(2):可以通过并查集来判断

 

 这里通过排序的方式来进行简单的说明,对所有的边的权重进行从小到大排序后:

(v1,v4)、(v6,v7)、(v1,v2)、(v3,v4)、(v2,v4)、(v1,v3)、(v4,v7)、(v3,v6)、(v5,v7)、(v4,v5)、(v4,v6)、(v2,v5)

 

        每次都收集权重最小的边,并判断待收集的边,是否会与已经收集的边构成环,直到收集到(n-1)条边为止,其中n表示的顶点个数:

(1)收集(v1,v4)

(2)收集(v6,v7)

(3)收集(v1,v2)

(4)收集(v3,v4)

(5)收集(v4,v7),为什么不收集(v2,v4)、(v1,v3)呢,因为收集它们会构成环

(6)收集(v5,v7),为什么不收集(v3,v6)、(v4,v5)、(v4,v6)、(v2,v5)呢,因为收集它们会构成环,至此,收集了6条边,一共7个顶点,所以已经收集完了。

 

Java代码实现:

思路:

(1)对边的权重从小到大排序

(2)判断待收集的边,是否会与已经收集的边构成环

(3)合并森林为树

(4)如果有n-1条边,就退出了

//Kruskal算法:求最小生成树
    public void kruskal() {
        ArrayList<Edge> list=new ArrayList<>();
        //初始化边
        for(int i=0;i<vertexList.length;i++) {
            for(int j=0;j<vertexList.length;j++) {
                //如果两个顶点有边
                if(mGraph[i][j]!=0 && mGraph[i][j]<Integer.MAX_VALUE) {
                    list.add(new Edge(i,j,mGraph[i][j]));
                }
            }
        }
        //对边按权值排序
        Collections.sort(list, new Comparator<Edge>() {

            @Override
            public int compare(Edge o1, Edge o2) {
                return o1.w-o2.w; //权值小的在前
            }
        });
        //初始化并查集,parent[i]=-1;表示这棵树只有它自己,一开始是n棵树
        for(int i=0;i<parent.length;i++) {
            parent[i]=-1;
        }
        //下面才是kruskal算法
        //list.size()就是边的数量
        int u,v,num=0,sum=0,index=0;
        char[] result=new char[2*vertexList.length-2]; //记录结果的数组,边的顺序
        System.out.println("下面是kruskal算法:");
        for(int i=0;i<list.size();i++) {
            Edge e=list.get(i);
            u=e.u;
            v=e.v;
            //如果顶点不属于同一个集合
            if(findRoot(u)!=findRoot(v)) {
                sum+=e.w;
                result[index++]=vertexList[u].value;
                result[index++]=vertexList[v].value;
                num++;
                union(u, v);
            }
            //如果有n-1条边,就退出了
            if(num==vertexList.length-1) {
                break;
            }
        }
        //打印边的信息
        System.out.println("kruskal包括的边依次是:");
        for(int i=0;i<result.length;i+=2) {
            System.out.println(result[i]+"--"+result[i+1]);
        }
        System.out.println("kruskal的最小权值:"+sum);
    }

看看关键代码:

            //如果顶点不属于同一个集合
            if(findRoot(u)!=findRoot(v)) {
                sum+=e.w;
                result[index++]=vertexList[u].value;
                result[index++]=vertexList[v].value;
                num++;
                union(u, v);
            }

判断顶点是否属于同一个集合,使用的是并查集的思想,findRoot方法如下:

    //查找某个顶点属于哪个集合
    public int findRoot(int v) {
        int root;  //集合的根节点
        for(root=v;parent[root]>=0;root=parent[root]);
        //路径压缩
        while(root!=v) {
            int tmp=parent[v];
            parent[v]=root;
            v=tmp;
        }
        return root;
    }

 初始化时,parent数组都为 -1 ,表示只有自己一个顶点,负数表示的是根节点,大小表示的是树中有多少个结点

所以,只要找到parent数组为负数的,那就是根结点,如果是同一个根结点,当然就是同一个集合了(在最小生成树中了)

这里还有一个关键的方法,就会合并森林的过程union( int u, int v),其实,这里可以随便哪一个作为根结点,但是,为了更新查找,需要使树更矮才行

    //将两个不同集合的元素进行合并,使两个集合中任两个元素都连通
    void union( int u, int v)
    {
        int r1 = findRoot(u), r2 = findRoot(v); //r1 为 u 的根结点,r2 为 v 的根结点
        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;
        }
    }

所以,这里会找结点数量多的根结点作为新的根结点,使形成的树更矮,并且更新结点数量 

 

 完整代码,请参考:https://blog.csdn.net/qiuxinfa123/article/details/83719789

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值