使用 UnionFind 判断他到底是不是你的远房表亲

简介: 不晓得大家晓不晓得并查集这个算法,我之前是没怎么听过(孤陋寡闻),其实这个算法是非常常见的一个算法,也是能用固定的套路去解决某一类问题,最为经典的就是亲戚问题。

并查集(UnionFind)常常用于处理一些不相交集合的合并及查询问题。

并查集(UnionFind)具有Union和Find两个功能

  • 合并(Union):将两个不相交的集合合并为一个集合。
  • 查询(Find):查询两个元素是否在同一个集合中(确定是否具有亲属关系,通过find可以获取其祖先)。

两棵树:

image.png

Union(1,5)之后

image.png

经典案例:亲戚问题

若某个人家族人员过于庞大,你如何判断其中的两个人是否具有亲戚关系呢?

x和y是亲戚关系,y是z的亲戚,那么x和z也是亲戚。如果x和y是亲戚,那么x的亲戚也是y的亲戚,y的亲戚也是x的亲戚。

这时候我们就可以使用并查集算法,将每一个亲戚关系使用Union关联起来,所有亲戚录入完毕,我们就可以使用并查集中的Find,可以通过Find获取他们的祖先,看看是否是同一个祖先,从而判断他们是不是亲戚。

时间复杂度:elogn(假设有e组集合)

并查集步骤:

  • 初始化:把每个点所在的集合初始化为其自身(时间复杂度O(n))

    image.png

  • 查找:查找两个元素所在的集合,寻找根节点(时间复杂度O(logn))

    • 递归查找
    • 找到根节点,递归返回时将递归路径所有节点标记为根节点(这样效率会更高,如果每个节点只存储上一级节点效率会更低)
  • 合并:如果两个元素属于不同的集合,将两个元素合并为一个集合(时间复杂度O(1))

    • 查找两个元素的根节点
    • 先祖不同时,将一个元素的祖先修改为另一个元素的祖先(擒贼先擒王

UnionFind中数组的意义: 下标代表树的节点,值代表节点的根节点(初始化假设自己是自己的根节点)

我们在使用union(1,2)时数组就会变为

image.png

上图的两个树用数组表示就是

image.png

使用union(1,5),数组就会变为下图,这就是擒贼先擒王(union(1,9)也是如此):

image.png

这使我们调用find(9)就会发生

find(9) -> find(5) -> find(1) 得到1同时将下标9修改为1

image.png

在需要使用并查集的场景中,每次使用的框架的都是差不多的,我们只需要对模板进行调整,即可满足大部分需求,下边是一个并查集的算法模板,通过对union简单的优化可以减少祖先设置的次数,从而实现quickUnion。

并查集模板代码:

package com.zhj.leetcode;
​
public class UnionFind {
    public int[] fa;
    public int[] rank;
  
    public UnionFind(int length){
        init(length);
    }
    
    /**
     * 初始化,将每个节点初始化为自身
     */
    public init(int length){
        fa = new int[length];
        rank = new int[length];
        for (int i = 0; i < length; i++) {
            fa[i] = i;
            rank[i] = 0;
        }
    }
​
    /**
     * 找到某个元素的根节点
     * @param x
     * @return
     */
    public int find(int x) {
        if (x == fa[x]) {
            return x;
        }
        return fa[x] = find(fa[x]);
    }
​
    /**
     * 合并两个元素为同一个根节点
     * @param x
     * @param y
     */
    public void union(int x, int y) {
        int faX = find(x);
        int faY = find(y);
        if (faX != faY) {
            fa[faX] = faY;
        }
    }
​
    /**
     * 合并两个元素为同一个根节点(优化)
     * @param x
     * @param y
     */
    public void quickUnion(int x, int y) {
        int faX = find(x);
        int faY = find(y);
        if (faX != faY) {
            if (rank[faX] > rank[faY]) {
                fa[faY] = faX;
            } else if (rank[faX] < rank[faY]){
                fa[faX] = faY;
            } else {
                fa[faY] = faX;
                rank[faX] += 1;
            }
        }
    }
}
目录
相关文章
|
4月前
|
Python
晶闸管阴阳极的判断
晶闸管阴阳极的判断
109 0
|
4月前
阿里云RPA元素出现后,有个返回结果 ,需要拿这个结果再去做判断吗?这个判断的操作 如何 处理
【2月更文挑战第8天】阿里云RPA元素出现后,有个返回结果 ,需要拿这个结果再去做判断吗?这个判断的操作 如何 处理
89 3
|
11月前
|
前端开发
12 # 根据 x 值来判断是成功还是失败
12 # 根据 x 值来判断是成功还是失败
33 0
|
4月前
|
C++
c++判断
c++判断
30 1
|
4月前
|
C语言
C判断
C判断
35 0
|
4月前
|
存储 C++
C++ 判断
C++ 判断
31 0
|
4月前
|
算法 前端开发 索引
判断对象是否为空
判断对象是否为空
51 0
|
4月前
|
小程序 区块链
血常规常见判断参数
血常规常见判断参数
36 0
|
11月前
|
程序员 C语言
C 判断
C 判断。
38 0
|
前端开发
你真的会判断对象是否为空吗?
一个小小的判空,却很可能让你吃了大亏,如果一个判空没有做好,那么里面的逻辑就完全裸露了,相信你一定吃过 `NullPointerException` 的苦头!
111 0