并查集概念和使用技巧

并查集是一种高效处理集合合并与查询的数据结构,适用于维护满足传递性条件的集合。通过找到满足条件的最小传递性条件,可以降低时间复杂度。在岛屿数量、相似字符串组等问题中,通过并查集可以简化判断条件,提高算法效率。并查集模板包括find()和union()方法,用于查找元素所属集合和合并集合。并查集可用于判断环路,同时与DFS/BFS等算法相比,能在一次遍历中找到所有连通组件或环路。
摘要由CSDN通过智能技术生成

并查集概念和使用技巧

1.概念

并查集常用来维护一些集合,每个集合中的任意两元素都满足某个条件x,并且条件x是一个传递性条件。支持查询**find()和合并2个集合union()**的操作,经过优化后的并查集完成上述操作的时间复杂度近似位O(1)。即:一个集合中的元素都满足某个传递性条件,如果a集合中某个元素和b集合中某个元素满足上传递性条件,则集合a,b合并为一个集合。

例子:
nums = [1,2,2,1,3,1]
假如此时要将nums中相等的元素分到一个集合中去,显然“两元素相等”就是一个传递性条件:a==b b==c >> a==c。
所以可以使用并查集来维护每个集合。
(1)首先并查集中有5个集合:[1],[2],[2],[1],[3],[1]
(2)遍历数组nums,对于当前遍历到的元素a,找到右边第一个等于它的元素b,如果a,b此时不在一个集合,则将包含a,b的集合合并;可以发现我们将“两元素相等”这个条件,转为"右边有一个元素相等",得到的最终并查集是一样的,但是可以降低时间复杂度;因为,使用原来的条件,在每次遍历到一个元素a时,都要把a后边所有的元素于a进行相等判断然后合并相等元素,但其实我们只需要找到右边第一个和a相等的元素并合并即可。
由此,在使用并查集问题时,需要找到满足原传递性条件的"最小"的传递性条件。

2.使用技巧

1.并查集主要用在:维护一些集合,每个集合中的任意两元素都满足某个条件x,并且条件x是一个传递性条件

并查集的本质就是维护传递性。
其实也可以使用DFS或者BFS,维护传递性的集合,但是每次遍历原题中的元素,都只会维护一个集合,如果题目只需要找到一个传递性的集合,则可以使用DFS或BFS,但是需要在一次遍历中维护一些集合,则需要使用并查集
并查集显然可以使用在连通性问题上。比如找到图上有多少的连通图(因为,连通性本身就满足传递性,a–>b b–>c >> a–c)

在使用中,一定要注意找到满足题意得最小传递性条件,从而不需要对每个元素a,判断所有其他元素是否和a满足条件;降低时间复杂度
在解决问题时,要首先明确两元素能合并的最小传递性条件。
2.并查集也可以用来判断环路,环路问题本来就是连通性问题(环路上的元素就是连通的)。判断方法:
每次将直接相连的元素U,V所在的集合合并为一个集合,并且合并U,V后,不会再后续遍历中重复合并U,V;那么,当我们每次需要合并的元素A,B已经在一个集合中时,就表示出现了环路。
同样使用DFS和BFS也可以找到环路,但是每次遍历都只能找到一条环路,但是并查集在遍历一次后,就可以找到所有的环路。

在使用DFS/BFS还是并查集维护传递性(连通性)时,我们要主要题目是找一个集合还是多个集合。

3.模板

//在实现时,使用根节点表示每个集合。
//详细参考书籍
class UnionFind{
    int [] parent; //保存每个元素a的父节点。
    int [] rank;   //保存每个元素a所属的集合中的元素个数。
    int count;     //有多少个集合。
    UnionFind(String [] strs){
        int n = strs.length;
        this.parent = new int[n];
        this.rank = new int[n];
        this.count = n;
        for(int i = 0;i<n;i++){
            parent[i] = i;
            rank[i] = 1;
        }
    }
	//找到下标为e的元素的所属集合。
    public int find(int e){
        int tmp = e;
        while (parent[tmp]!=tmp){
            tmp = parent[tmp];
        }
        parent[e] = tmp;
        return tmp;
    }
	//合并2个集合。
    public Boolean union(int a,int b){
        int roota = find(a);
        int rootb = find(b);
        if(roota==rootb) return false;
        if(rank[roota]<rank[rootb]){
            parent[roota] = rootb;
            rank[rootb] += rank[roota];
        }else {
            parent[rootb] = roota;
            rank[roota] += rank[rootb];
        }
        count--;
        return true;
    }

4.例子

1.岛屿数量

在这里插入图片描述

任意2个岛屿是一个岛屿的条件可以表示为条件1:

  • 岛屿a的上下左右有岛屿b,则a,b是一个岛屿。
  • 岛屿a,b是一个岛屿,且岛屿b,c也是一个岛屿,则岛屿a,c是一个岛屿。

可以看出上述条件满足传递性,因此可以使用并查集维护每个满足上述条件的集合,每一个集合就表示一个岛屿,最终有多少的集合,就有多少的岛屿。

如果只使用条件1,则对于任意一个元素岛屿a,要比较它的上下左右是不是岛屿,是则合并包含2个岛屿的集合;所以对于每一个元素要比较4次;可以改变条件1,使得只需要比较2次。

条件2:

  • 岛屿a的下右有岛屿b,则a,b是一个岛屿。
  • 岛屿a,b是一个岛屿,且岛屿b,c也是一个岛屿,则岛屿a,c是一个岛屿。

把每次需要比较上下左右,改为比较下右,当然也可以比较(上左,上右,下左),也能满足原题,所以可以将条件最小化

2.相似字符串组

在这里插入图片描述

很明显,此题也是维护一些集合,每个集合中的元素都满足相似条件,并且相似条件是一个传递性条件,则使用并查集。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值