给我三分钟,带你领略热血江湖中的并查集算法

简介: 给我三分钟,带你领略热血江湖中的并查集算法

一、什么是并查集

并查集被很多OIer认为是最简洁而优雅的数据结构之一,主要用于处理一些不相交集合的合并问题,并支持两种操作:

  • 合并(Union):把两个不相交的集合合并为一个集合。
  • 查询(Find):查询两个元素是否在同一个集合中。

当然,这样的定义让人感觉摸不着头脑,我们来一个样例进行分析。

二、实现并查集

并查集的初始化:

class UnionAndFind() {
    // 自己的大哥是谁
    private int[] parent;
    // 自己的队伍有多少人
    private int[] size;
    // 江湖还有几个派系
    int sets;
    // 初始化
    // 每个人的大哥都是自己
    // 每个队伍的人数都为1
    public UnionAndFind(int N) {
        parent = new int[N];
        size = new int[N];
        help = new int[N];
        sets = N;
        for (int i = 0; i < N; i++) {
            parent[i] = i;
            size[i] = 1;
        }
    }
}

并查集的 查询(Find) 方法:这里的时间复杂度可以降低到O(1)级别,不敢相信吧,那就往下看吧

  // 查询a和b在不在一个阵营中
  public boolean isSameSet(int a, int b) {
        return find(a) == find(b);
    }
  // 看一下你的最终大哥是谁
    public int find(int i){
        while(i != parent[i]){
            i = parent[i];
        }
        return i;
    }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                

并查集的 合并(Union)方法:先去找到两个参数的最终大哥,小弟少的大哥跟着小弟多的大哥混

  public void union(int a, int b) {
        int A = find(a);
        int B = find(b);
        if (A != B) {
            if (size[A] >= size[B]) {
                size[A] += size[B];
                parent[B] = A;
            } else {
                size[B] += size[A];
                parent[A] = B;
            }
            sets--;
        }
    }

三、真题训练

547. 省份数量

  • 初始化数组,拷贝上述并查集即可
public int findCircleNum(int[][] isConnected) {
        int N = isConnected.length;
        UnionAndFind unionFind = new UnionAndFind(N);
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                if (isConnected[i][j] == 1 && i != j) {
                    unionFind.union(i, j);
                }
            }
        }
        return unionFind.sets;
    }

3b88c397f83542ab8cfd999ac714ba0b.png

四、路径压缩优化

我们目前的并查集已经可以解决大部分问题,但是,我们能够对目前的并查集版本进行路径压缩优化。

我们可以发现,在我们 find() 方法时,我们会进行一个 while() 循环的查找大哥的操作,这种操作十分的浪费时间,有没有什么办法可以进行下优化呢?

如下所示:

我们可以看到对于6、5、3之类来说,每次进行 find 查询的时候,都需要经过2~3次的循环才能够找到1。

如果我们在进行查找的时候,保存一下值,然后重新挂到1的上面,比如:

我要查询下6,肯定会查询找5、3,这个时候,我们把6、5、3保存到临时数组中,在查询完毕后,将这些数组直接挂到1的下面,这样的话,下次查询会降低循环的次数。


当我们查询的次数远远大于我们的数据量时,这个时候 find() 的操作就已经变成了一个O(1)级别的查询时间。

public int find(int i) {
        int h = 0;
        while (i != parent[i]) {
            help[h++] = i;
            i = parent[i];
        }
        for (h--; h >= 0; h--) {
            parent[help[h]] = i;
        }
        return i;
    }

五、总结

并查集算法通常用在处理一些不交集(Disjoint Sets)的合并及查询问题

力扣也有并查集的 tag 专栏:并查集

大家可以好好体会一下路径压缩的奇妙,通过不断的路径压缩,可以将我们的时间复杂度降低到O(1)


这里可能有小伙伴有疑问,为啥是O(1),你就算挂在1的下面,不也得查询两次嘛


对于时间复杂度来说,只要是常数的次数都可以认定为O(1)的时间复杂度,况且,你也可以使用HashMap来进行存储



相关文章
|
21天前
|
机器学习/深度学习 存储 算法
【算法沉淀】刷题笔记:并查集 带权并查集+实战讲解
【算法沉淀】刷题笔记:并查集 带权并查集+实战讲解
|
21天前
|
算法
并查集的实现【学习算法】
并查集的实现【学习算法】
24 0
|
12天前
|
算法 C++
c++算法学习笔记 (16) 并查集
c++算法学习笔记 (16) 并查集
|
21天前
|
算法 测试技术
并查集算法
并查集算法
|
21天前
|
算法
算法专栏 ---- trie树,并查集
算法专栏 ---- trie树,并查集
|
11月前
|
算法
算法 - 蓝桥杯并查集题型
算法 - 蓝桥杯并查集题型
|
12月前
|
存储 算法
从小白开始刷算法 并查集篇 leetcode.547
从小白开始刷算法 并查集篇 leetcode.547
|
12月前
|
存储 算法 索引
从小白开始刷算法 并查集篇 leetcode.200
从小白开始刷算法 并查集篇 leetcode.200
|
存储 算法 API
LeetCode算法小抄 -- 经典图论算法 之 并查集算法
LeetCode算法小抄 -- 经典图论算法 之 并查集算法
|
5天前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于DCT变换和位平面分解的数字水印嵌入提取算法matlab仿真
这是一个关于数字水印算法的摘要:使用MATLAB2022a实现,结合DCT和位平面分解技术。算法先通过DCT变换将图像转至频域,随后利用位平面分解嵌入水印,确保在图像处理后仍能提取。核心程序包括水印嵌入和提取,以及性能分析部分,通过PSNR和NC指标评估水印在不同噪声条件下的鲁棒性。