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

850 阅读3分钟

「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战」。

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

并查集(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;
            }
        }
    }
}