并查集笔记

并查集是一种数据结构,用于高效处理网络中节点的连接状态。通过合并(union)和判断连接(isConnected)两个基本操作,实现元素集合的动态维护。常见的并查集实现包括数组表示和树形结构,其中树形结构通过路径压缩和基于rank的优化提升效率。在大规模数据下,路径压缩和优化后的并查集能显著减少查找和合并的时间复杂度。
摘要由CSDN通过智能技术生成

并查集Union Find

一、并查集解决什么问题?

可以非常高效的解决网络中节点的连接问题

二、操作

1>合并 union(p,q)

2>是否连接 isConnected(p,q)

注意:在使用并查集时,我们并不关心元素内容,因此可以使用数组保存元素,p和q分别对

应元素所在数组的索引

接口:

public interface UF {
    // 获取元素的个数
    int getSize();

    // 合并操作
    void union(int p, int q);

    // 判断是否连接
    boolean isConnection(int p, int q); // p和q代表的是元素所在数组的索引
}

三、并查集的基本数据表示

在这里插入图片描述

索引为0和3的元素是否连接?因为它们同属于一个集合,所以是连接的

索引为0和5的元素是否连接?因为它们不属于同一个集合,所以是不连接的

四、代码表示

  int[] ids; // 元素所属的集合
  public UnionFind01(int length) {
        ids = new int[length];
        for (int i = 0; i < ids.length; i++) {
            ids[i] = i;
        }
    }

其中:

id数组,代表元素所属的集合

默认情况下,每个元素都是独立的,相当于每个元素所属不同的集合,没有元素相连接。

在这里插入图片描述

五、合并操作

在这里插入图片描述

合并元素索引为1和4的集合 union(1,4)

在这里插入图片描述

所以复杂度为O(n)

六、真正的并查集实现

将每个元素看作是一个结点,结点之间相连接,形成树的结构
在这里插入图片描述

结点3与结点2相连接

将结点1与结点3相合并,只要将结点1指向结点3所在那棵树的根结点。

在这里插入图片描述

public class UnionFind01 implements UF {

    int[] ids; // 元素所属的集合

    public UnionFind01(int length) {
        ids = new int[length];
        for (int i = 0; i < ids.length; i++) {
            ids[i] = i;
        }
    }

    // 辅助的方法 (根据元素所在数组的索引找所在集合的编号)
    int find(int index) {
        return ids[index];
    }

    @Override
    public int getSize() {
        return ids.length;
    }

    @Override
    public void union(int p, int q) {
        int pBoxNum = find(p);
        int qBoxNum = find(q);
        if (pBoxNum != qBoxNum) {
            for (int i = 0; i < ids.length; i++) {
                if (find(i) == qBoxNum) {
                    ids[i] = pBoxNum;
                }
            }
        }
    }

    @Override
    public boolean isConnection(int p, int q) {
        int pBoxNum = find(p);
        int qBoxNum = find(q);
        return pBoxNum == qBoxNum;
    }
}

七、数据表示

在这里插入图片描述

在这里插入图片描述

依次进行union(6,5).union(9,4),union(2,1),union(5,0),union(7,2),union(6,2),最终得到

在这里插入图片描述

public class UnionFind02 implements  UF {

    int[] parents;

    public UnionFind02(int length){
        parents = new int[length];
        for (int i = 0; i < parents.length; i++) {
            parents[i] = i;
        }
    }

    private int find(int index){
        while (index != parents[index]) {
            index = parents[index];
        }
        return index;
    }

    @Override
    public int getSize() {
        return parents.length;
    }

    @Override
    public void union(int p, int q) {
        int pBoxNum = find(p);
        int qBoxNum = find(q);
        if (pBoxNum != qBoxNum) {
            parents[pBoxNum] = qBoxNum;
        }
    }

    @Override
    public boolean isConnection(int p, int q) {
        int pBoxNum = find(p);
        int qBoxNum = find(q);
        return pBoxNum == qBoxNum;
    }
}

判断索引为6和4的元素是否连接?

索引为6的元素所在树的根节点为1,索引为4的元素所在树的根节点为8,所以没有连接。

八、测试并查集

public class DemoTest {

    public static void test(UF uf, int count) {
        long startTime = System.nanoTime();
        int size = uf.getSize();
        Random random = new Random();
        for (int i = 0; i < 1000; i++) {
            int p = random.nextInt(size);
            int q = random.nextInt(size);
            uf.union(p, q);
        }
        for (int i = 0; i < count; i++) {
            int p = random.nextInt(size);
            int q = random.nextInt(size);
            uf.isConnection(p, q);
        }
        long endTime = System.nanoTime();
        System.out.println("Times:" + (endTime - startTime) / 1000000000.0);
    }

    public static void main(String[] args) {
        int size = 10000000;
        int count = 10000000;
        UF uf = new UnionFind01(size);
        test(uf,count);
        UF uf2 = new UnionFind02(size);
        test(uf2,count);
/*        UF uf3 = new UnionFind03(size);
        test(uf3,count);
        UF uf4 = new UnionFind04(size);
        test(uf4,count);
        UF uf5 = new UnionFind05(size);
        test(uf5,count);
        UF uf6 = new UnionFind06(size);
        test(uf6,count);*/
    }
}

结果会发现:当数据size = 100000,count = 100000时,使用树结构形式存储连接关系的并查集

比使用数组的形式耗时要长。归其原因:不管是查询操作还是合并操作,使用树结构形式的

并查集的复杂度为O(h),在极端的情况下,这棵树的深度会比较大。

在这里插入图片描述

九、基于size的优化

在这里插入图片描述

/**
 * 基于Size优化
 */
public class UnionFind03 implements  UF {

    int[] parents;
    int[] size; // 来保存每棵树结点的个数

    public UnionFind03(int length){
        parents = new int[length];
        size = new int[length];
        for (int i = 0; i < parents.length; i++) {
            parents[i] = i;
            size[i]= 1;
        }
    }

    private int find(int index){
        while (index != parents[index]) {
            index = parents[index];
        }
        return index;
    }

    @Override
    public int getSize() {
        return parents.length;
    }

    @Override
    public void union(int p, int q) {
        int pBoxNum = find(p);
        int qBoxNum = find(q);
        if (pBoxNum != qBoxNum) {
            if (size[pBoxNum] > size[qBoxNum]) {
                parents[qBoxNum] = pBoxNum;
                size[pBoxNum] += size[qBoxNum];
            } else {
                parents[pBoxNum] = qBoxNum;
                size[qBoxNum] += size[pBoxNum];
            }
        }
    }

    @Override
    public boolean isConnection(int p, int q) {
        int pBoxNum = find(p);
        int qBoxNum = find(q);
        return pBoxNum == qBoxNum;
    }
}

解决的思路:将元素个数少的树合并到元素个数多的树上。需要开辟空间,存放每棵树的元

素个数。 【int[] size】

十、基于rank优化

基于size优化的缺点:
在这里插入图片描述
在这里插入图片描述

/**
 * 基于高度进行优化
 */
public class UnionFind04 implements UF {

    int[] parents;
    int[] rank; // 基于高度进行优化 ---由低到高

    public UnionFind04(int length) {
        parents = new int[length];
        rank = new int[length];
        for (int i = 0; i < parents.length; i++) {
            parents[i] = i;
            rank[i] = 1;
        }
    }

    private int find(int index) {
        while (index != parents[index]) {
            index = parents[index];
        }
        return index;
    }

    @Override
    public int getSize() {
        return parents.length;
    }

    @Override
    public void union(int p, int q) {
        int pBoxNum = find(p);
        int qBoxNum = find(q);
        if (pBoxNum != qBoxNum) {
            if (rank[pBoxNum] > rank[qBoxNum]) {
                parents[qBoxNum] = pBoxNum;
            } else if (rank[qBoxNum] > rank[pBoxNum]) {
                parents[pBoxNum] = qBoxNum;
            } else {
                parents[pBoxNum] = qBoxNum;
                rank[qBoxNum] += 1;
            }
        }
    }

    @Override
    public boolean isConnection(int p, int q) {
        int pBoxNum = find(p);
        int qBoxNum = find(q);
        return pBoxNum == qBoxNum;
    }
}

十一:路径压缩

在这里插入图片描述
在这里插入图片描述

方法:parent[p]= parent[parent[p]] 路径压缩发生的时机:find操作

在查询时执行,执行

在这里插入图片描述

/**
 * 进行路径压缩
 */
public class UnionFind05 implements UF {
    int[] parents;

    public UnionFind05(int length) {
        parents = new int[length];
        for (int i = 0; i < parents.length; i++) {
            parents[i] = i;
        }
    }


    private int find(int index) {
        while (index != parents[index]) {
            // 路径压缩
            parents[index] = parents[parents[index]];
            index = parents[index];
        }
        return index;
    }

    @Override
    public int getSize() {
        return parents.length;
    }

    @Override
    public void union(int p, int q) {
        int pBoxNum = find(p);
        int qBoxNum = find(q);
        if (pBoxNum != qBoxNum) {
            parents[qBoxNum] = pBoxNum;
        }
    }

    @Override
    public boolean isConnection(int p, int q) {
        int pBoxNum = find(p);
        int qBoxNum = find(q);
        return pBoxNum == qBoxNum;
    }
}

/**
 * 进行路径压缩
 */
public class UnionFind06 implements UF {
    int[] parents;

    public UnionFind06(int length) {
        parents = new int[length];
        for (int i = 0; i < parents.length; i++) {
            parents[i] = i;
        }
    }


    private int find(int index) {
        if (index != parents[index]) {
            parents[index] = find(parents[index]);
        }
        return parents[index];
    }

    @Override
    public int getSize() {
        return parents.length;
    }

    @Override
    public void union(int p, int q) {
        int pBoxNum = find(p);
        int qBoxNum = find(q);
        if (pBoxNum != qBoxNum) {
            parents[qBoxNum] = pBoxNum;
        }
    }

    @Override
    public boolean isConnection(int p, int q) {
        int pBoxNum = find(p);
        int qBoxNum = find(q);
        return pBoxNum == qBoxNum;
    }
}
public int getSize() {
    return parents.length;
}

@Override
public void union(int p, int q) {
    int pBoxNum = find(p);
    int qBoxNum = find(q);
    if (pBoxNum != qBoxNum) {
        parents[qBoxNum] = pBoxNum;
    }
}

@Override
public boolean isConnection(int p, int q) {
    int pBoxNum = find(p);
    int qBoxNum = find(q);
    return pBoxNum == qBoxNum;
}

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值