Python 每日一记262@Java并查集实现

什么并查集

在这里插入图片描述

java代码

特别注意:并查集数组中,索引为元素,值为分组标志,比如城市1,2,3就是索引,值都是1的话,表示都在组1中。

在这里插入代码片package mypackage;

import java.util.Scanner;

//并查集
class UF{
    //并查集数组,索引为元素,值为分组标志,比如城市1,2,3就是索引,值都是1的话,表示都在组1中
    private int[] arr;
    private int count;//分组个数,多少个组

    //构造方法,传入分组的个数,默认每个元素单独一组
    public UF(int N) {
        this.count = N;
        this.arr=new int[N];
        //每个元素一组
        for (int i = 0; i < arr.length; i++) {
            arr[i]=i;
        }
    }
//获取分组的个数
    public int getCount(){
        return count;
    }
//查找某个元素所在的组,注意元素就是索引,所在的组就是数组的值
    public int find(int p){
        return  arr[p];
    }
//判断是否在一个分组中,即查看是否这两个索引对应的值是否相等
    public boolean connected(int p,int q){
        return find(p)==find(q);
    }
//合并分组,即使得两个元素处于同一组,即使得两个索引处的值相等
    public void union(int p,int q){
//        如果已经在一组中,直接返回
        if (connected(p,q)){
            return;
        }else {
//            获取p q所在的分组
            int pGroup=find(p);
            int qGroup=find(q);

//            注意这里是将和p是一组的所有的元素的组标识都变成q的组标识,而不是只变化一个,
//            因此要用循环,而不是只arr[p]=qGroup
            for (int i = 0; i < arr.length; i++) {
                if (arr[i]==pGroup){
                    arr[i]=qGroup;
                }
            }
//            减少分组个数
            count--;
        }
    }
}

//测试
public class MyJava {

    public static void main(String[] args) {
        UF uf=new UF(5);
        System.out.println("默认并查集中有:"+uf.getCount()+"个分组");
        Scanner sc=new Scanner(System.in);
        while (true){
            System.out.println("请输入第一个要合并的元素:");
            int p=sc.nextInt();
            System.out.println("请输入第二个要合并的元素:");
            int q=sc.nextInt();
            if (uf.connected(p,q)){
                System.out.println("这两个元素已经在一个分组中");
            }else {
                uf.union(p,q);
                System.out.println("合并完毕,当前还有"+uf.getCount()+"个分组");
            }
        }
    }
}

在这里插入图片描述

并查集改进1

之前在合并的时候,union使用还是使用了循环,如果数据量较大的话,需要循环将组的标志更改,性能不佳,为了改进这个性能,进行修改。

/*
* 改进的地方主要是find和union方法
* 现在并查集数组,索引为元素,值为元素分组的父节点,根据父节点找根节点,根节点才是分组标志
* 比如索引0对应的值为2,然后再找索引2的值为4,索引4的值为4,直到索引对应的值和索引相同,即停止,表示索引0对应的分组为4
*
* */
package mypackage;

import java.util.Scanner;

//并查集
class UF{
    //并查集数组,索引为元素,值为元素分组的父节点,根据父节点找根节点,根节点才是分组标志
    // 比如索引0对应的值为2,然后再找索引2的值为4,索引4的值为4,直到索引对应的值和索引相同,即停止,表示索引0对应的分组为4
    private int[] arr;
    private int count;//分组个数,多少个组

    //构造方法,传入分组的个数,默认每个元素单独一组
    public UF(int N) {
        this.count = N;
        this.arr=new int[N];
        //每个元素一组
        for (int i = 0; i < arr.length; i++) {
            arr[i]=i;
        }
    }
//获取分组的个数
    public int getCount(){
        return count;
    }
//查找某个元素所在的组,注意元素就是索引,所在的组就是数组的值
    /*改进前
    public int find(int p){
        return  arr[p];
    }*/
//    改进后
    public int find(int p){
        while (true){
            if (p==arr[p]){
                return p;
            }else {
                p=arr[p];
            }
        }
    }
//判断是否在一个分组中,即查看是否这两个索引对应的值是否相等
    public boolean connected(int p,int q){
        return find(p)==find(q);
    }
//合并分组,即使得两个元素处于同一组,即使得两个索引处的值相等
    /*改进前
    public void union(int p,int q){
//        如果已经在一组中,直接返回
        if (connected(p,q)){
            return;
        }else {
//            获取p q所在的分组
            int pGroup=find(p);
            int qGroup=find(q);

//            注意这里是将和p是一组的所有的元素的组标识都变成q的组标识,而不是只变化一个,
//            因此要用循环,而不是只arr[p]=qGroup
            for (int i = 0; i < arr.length; i++) {
                if (arr[i]==pGroup){
                    arr[i]=qGroup;
                }
            }
//            减少分组个数
            count--;
        }
    }*/

    //    改进后
    public void union(int p,int q){
        int pGroup=find(p);
        int qGroup=find(q);
        if (pGroup==qGroup){
            return;
        }else {
//            直接让根节点的值为qGroup,因为根节点才是组的标识
            arr[pGroup]=qGroup;
            count--;
        }
    }
}

//测试
public class MyJava {

    public static void main(String[] args) {
        UF uf=new UF(5);
        System.out.println("默认并查集中有:"+uf.getCount()+"个分组");
        Scanner sc=new Scanner(System.in);
        while (true){
            System.out.println("请输入第一个要合并的元素:");
            int p=sc.nextInt();
            System.out.println("请输入第二个要合并的元素:");
            int q=sc.nextInt();
            if (uf.connected(p,q)){
                System.out.println("这两个元素已经在一个分组中");
            }else {
                uf.union(p,q);
                System.out.println("合并完毕,当前还有"+uf.getCount()+"个分组");
            }
        }
    }
}

在这里插入图片描述

并查集改进2

以上虽然我们改变了union方法,使得不用循环,但是find方法内却使用循环,如果链很长,find同样效率不高,导致链很长的原因之一可能是在合并存在问题,上面合并时,我们是暴力的合并,如果将大树的根节点指向小树的根节点的合并方式,则会增加数的深度,反之如果将小树合并到大树上,则深度不会增加,因此我们可以将组内元素较小的组合并到较大组中,这样就会降低find的查找深度。

/*
* 改进的地方主要是union方法
* 如果让较小的组合并到较大的组上,树的深度就可能降低,增加效率
* */
package mypackage;

import java.util.Scanner;

//并查集
class UF{
    //并查集数组,索引为元素,值为元素分组的父节点,根据父节点找根节点,根节点才是分组标志
    // 比如索引0对应的值为2,然后再找索引2的值为4,索引4的值为4,直到索引对应的值和索引相同,即停止,表示索引0对应的分组为4
    private int[] arr;
    private int count;//分组个数,多少个组
    private int[] sz;//记录元素对应的分组中元素的个数

    //构造方法,传入分组的个数,默认每个元素单独一组
    public UF(int N) {
        this.count = N;
        this.arr=new int[N];
        //每个元素一组
        for (int i = 0; i < arr.length; i++) {
            arr[i]=i;
        }

        this.sz=new int[N];
        //每个元素一组,初始情况下,每个组一个元素
        for (int i = 0; i < arr.length; i++) {
            sz[i]=1;
        }

    }
//获取分组的个数
    public int getCount(){
        return count;
    }
//查找某个元素所在的组,注意元素就是索引,所在的组就是数组的值
    /*改进前
    public int find(int p){
        return  arr[p];
    }*/
//    改进后
    public int find(int p){
        while (true){
            if (p==arr[p]){
                return p;
            }else {
                p=arr[p];
            }
        }
    }
//判断是否在一个分组中,即查看是否这两个索引对应的值是否相等
    public boolean connected(int p,int q){
        return find(p)==find(q);
    }
//合并分组,即使得两个元素处于同一组,即使得两个索引处的值相等
    /*改进前
    public void union(int p,int q){
//        如果已经在一组中,直接返回
        if (connected(p,q)){
            return;
        }else {
//            获取p q所在的分组
            int pGroup=find(p);
            int qGroup=find(q);

//            注意这里是将和p是一组的所有的元素的组标识都变成q的组标识,而不是只变化一个,
//            因此要用循环,而不是只arr[p]=qGroup
            for (int i = 0; i < arr.length; i++) {
                if (arr[i]==pGroup){
                    arr[i]=qGroup;
                }
            }
//            减少分组个数
            count--;
        }
    }*/
/*
    //    第一次改进后
    public void union(int p,int q){
        int pGroup=find(p);
        int qGroup=find(q);
        if (pGroup==qGroup){
            return;
        }else {
//            直接让根节点的值为qGroup,因为根节点才是组的标识
            arr[pGroup]=qGroup;
            count--;
        }
    }*/

//    第二次改进
//    比较组内元素的个数,每次都将较小的组合并到较大的组上,且更改组内元素个数
    public void union(int p,int q){
        int pGroup=find(p);
        int qGroup=find(q);
        if (pGroup==qGroup){
            return;
        }else {
//            pGroup组内元素个数较小
            if (sz[pGroup]<sz[qGroup]){
                arr[pGroup]=qGroup;
                sz[qGroup]=sz[qGroup]+sz[pGroup];
            }else {
//            qGroup组内元素个数较小
                arr[qGroup]=pGroup;
                sz[pGroup]=sz[pGroup]+sz[qGroup];
            }
            count--;
        }
    }
}

//测试
public class MyJava {

    public static void main(String[] args) {
        UF uf=new UF(5);
        System.out.println("默认并查集中有:"+uf.getCount()+"个分组");
        Scanner sc=new Scanner(System.in);
        while (true){
            System.out.println("请输入第一个要合并的元素:");
            int p=sc.nextInt();
            System.out.println("请输入第二个要合并的元素:");
            int q=sc.nextInt();
            if (uf.connected(p,q)){
                System.out.println("这两个元素已经在一个分组中");
            }else {
                uf.union(p,q);
                System.out.println("合并完毕,当前还有"+uf.getCount()+"个分组");
            }
        }
    }
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值