并查集
并查集是一种树型的数据结构,用于处理两个没有交集的集合的合并或者查找问题。由它的名字就可以看出,它的主要操作一是合并,二是查找。
初始时,所有的元素都不相交。通过多次的合并,最终会合并成多个集合或者一个大集合。
查:查看两个元素是否在同一集合里
并:若是两个元素不属于同一集合,就把两个集合进行合并
并查集思想:在一个集合里选择一个元素作为该集合的代表节点
如果想要判断两个节点是否属于同一棵树中,那这两个节点所在的树的根节点相同时,则这两个节点属于同一棵树。代表节点就相当于树的根节点,如果两个元素所在集合的代表集合相同,则这两个元素属于同一个集合
例如有六个节点
首先把六个节点都看作六个集合,每个集合中的代表元素为本身,
此时用一个parent数组表示各节点的父节点,此时为各节点的本身
用一个数组size[]记录每个根节点(代表元素)对应的树的深度(与后面的按秩合并有关),count表示此时有多少个集合,此时共有六个集合
若要将六个集合进行合并,首先就需要进行查操作,查看此时两个元素是否在同一个集合中,不在时就可以进行合并操作。通过判断元素所在集合中的代表元素是否相同来判断。
public boolean connected(int index1,int index2){
return find(index1)==find(index2);
}
public int find(int index){
while(index!=parent[index]){
parent[index] = parent[parent[index]];
index = parent[index];
}
return index;
}
此时1 ,3元素不属于同一集合,可以进行合并如下图
此时parent数组为
count为5全部合并后图假设为(下图只是一种情况)
此时parent为
count为1
public void union(int index1,int index2){
int root1 = find(index1);
int root2 = find(index2);
if(root1==root2){return;}
if(size[root1]>size[root2]){
parent[root2] = root1;
size[root1] += size[root2];
} else {
parent[root1] = root2;
size[root2] += size[root1];
}
count--;
}
怎样合并才能让下一次查找集合中代表元素的次数尽可能少呢。(可类比于在一个树中怎样才能尽快从一个节点出发,找到这棵树的根节点,那就是让这棵树的树高尽可能小)
有两种方法:路径压缩,按照秩进行合并
什么是路径压缩
最简单的并查集效率是比较低的。例如,来看下面这个场景:
我们需要把两个集合合并,可合并为
当有个节点4需要合并时可能会合并为
随着元素的增加,此时从一个节点出发查找代表元素需要的时间会越来越长(此时4为代表元素)我们可以使用路径压缩的方法。既然我们只关心一个元素对应的根节点,那我们希望每个元素到根节点的路径尽可能短,最好只需要一步,像这样:
关键在于在路径上的每个节点都可以直接连接到根上让树扁平化`
int find(int x)
{
if(x == parent[x])
return x;
else{
parent[x] = find(parent[x]); //父节点设为根节点
return parent[x]; //返回父节点
}
}
按秩合并
路径压缩需要把所有节点父节点全部改为代表元素,如果此时集合较为复杂会比较消耗时间。例如我们需要把这两个集合进行合并时
只有把深度较深集合的代表元素作为合并后集合的代表元素即总是将更小的树连接至更大的树上,如果深度相同且根节点不同,则新的根节点的深度+1
用一个数组size[]记录每个根节点(代表元素)对应的树的深度
public void union(int index1,int index2){
int root1 = find(index1);
int root2 = find(index2);
if(root1==root2){return;}
if(size[root1]>size[root2]){
parent[root2] = root1;
} else if(size[root1]<size[root2]){
parent[root1] = root2;
} else () {
size[root2] ++;
}
if(size[root1]==size[root2] && x != y){
parent[root2] = root1;
}
count--;
}
完整代码如下
class Union {
int parent[]; //该节点的父节点
int size[]; //该节点到其他节点的距离
int count; //有多少个连通图
public Union(int n){ //初始化,每个节点的父节点为自身
parent = new int[n];
size = new int[n];
count = n;
for(int i=0;i<n;i++){
parent[i] = i;
size[i] = 1;
}
}
public void union(int index1,int index2){
int root1 = find(index1);
int root2 = find(index2);
if(root1==root2){return;}
if(size[root1]>size[root2]){
parent[root2] = root1;
size[root1] += size[root2];
} else {
parent[root1] = root2;
size[root2] += size[root1];
}
count--;
}
public int find(int index){
while(index!=parent[index]){
parent[index] = parent[parent[index]];
index = parent[index];
}
return index;
}
public boolean connected(int index1,int index2){
return find(index1)==find(index2);
}
public int size(){
return this.count;
}
}