并查集概念和使用技巧
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.相似字符串组
很明显,此题也是维护一些集合,每个集合中的元素都满足相似条件,并且相似条件是一个传递性条件,则使用并查集。