一、什么是并查集?
并查集是一种数据结构,通常用来判断元素之间是否属于同一个集合。在LeetCode200.岛屿数量中就可以使用并查集解决。
二、基本思路
使用数组存储每个元素所在的集合。例如union[2] == 1,代表第三个元素是集合1里的元素。
1、初始化
使用并查集,首先对并查集进行初始化,代码示例如下:
private int[] union;
private int size;
public void initUnion(){
union = new int[size];
for(int i = 0; i < size; i++){
union[i] = i;
}
}
2、查找元素属于哪个集合
public int find(int element){
return union[element];
}
3、判断两个元素是否属于同一个集合
public boolean isConnected(int element1,int element2){
return find(element1) == find(element2);
}
4、合并两个元素所在的集合
public void unionElements(int element1,int element2){
int set1 = find(element1);
int set2 = find(element2);
//如果两个集合不同就合并
if(set1 != set2){
for(int i = 0; i < size; i++){
if(find(i) == set1){
union[i] = set2;
}
}
}
}
三、对基础并查集的优化
1、快速union
基础的并查集的union过程时间复杂度是O(N),每一次合并都需要遍历全部元素。
改进:union中保存的是每个元素的父亲编号。例如:union[1] == 3,union[3] == 6,union[6] == 6
即元素1是和3一个集合的,而元素3和元素6是一个集合的,元素6集合的代表元素就是他自己。所以,元素1、3、6都属于一个集合。
现在要合并集合怎么办呢?让最大的父亲编号变成新的代表元素即可。
public class Union{
private int[] parent;
private int size;
//初始化并查集
public void initUnion(int size){
this.size = size
parent = new int[size];
for(int i = 0; i < size; i++){
parent[i] = i;
}
}
//查找代表元素
public int find(int element){
while(element != parent[element]){
element = parent[element];
}
return element;
}
//是否属于同一个集合
public boolean isConnected(int element1,int element2){
return find(element1) == find(element2);
}
//合并
public void union(int element1,int element2){
int set1 = find(element1);
int set2 = find(element2);
if(set1 == set2) return;
parent[set1] = set2;
}
}
2、如何决定谁合并到谁的集合里呢?
1)按重量合并
即谁下面的元素多,谁就是新的大集合的老大。
对于集合新加一个数组weight[],用来存储集合中元素的个数。
在union操作时,谁的集合大,就用谁当代表元素,同时,weight[element]要加上另一个集合的weight。
2)按深度合并
如果按重量合并,合并到一定程度后,集合就像链表一样成线性的了,find()的时间复杂度就会趋近于O(N)。
新增加一个数组height[],用来记录集合的深度。
所以,可以按深度合并,谁的深度高,就以谁为老大,这样集合的最大深度就不会改变。
3、路径压缩
路径压缩的目的是为了处理深度较大的元素。
处理方式很简单,在find()中,将深度大的元素的parent指向自己父亲的父亲
parent[element] = parent[parent[element]];
这样就减小了深度。
一般只在基于重量的并查集中使用,因为高度的减小并不会影响重量,而高度重新计算很麻烦。