1.基础
并查集是一种用于处理不相交集合的数据结构,通常用树结构或数组结构来实现,从名字就可以看出它有两个功能,一个是并即合并,一个是查即查询
在讲之前,要先明白一个设定,如果A和B在同一个集合,B和C在同一个集合,那么A和C在同一个集合或A,B,C在同一个集合
下面讲解的时候是把集合看成一棵树,但实际上是用数组来实现
先举个例子来讲解一下我们需要解决的问题
有1,2,3,4,5,6总共6个数,1和2,3和5,1和6分别在同一个集合中,问其中任意两个数是否在同一个集合中,因为我们是把集合看成树,那么就是看这两个数是否在同一棵树中,那么只需要判断这两个数所在的树的根结点是否是同一个数,那么就可以看成下面这幅图。
因为我们要从下往上去找寻根结点,而且父亲的儿子不唯一,儿子的父亲是唯一的,所以不再是让父亲指向儿子,而是让儿子指向父亲。至于集合中的数谁是谁的父亲,都无所谓,只要连起来就行。那为了判断两个数是否在同一棵树中,首先要把每个数都初始化为一棵树,该树的根节点就是这个数,在这之前我们要定义一个数组,如下所示
注意,这里的数组的定义是i的父亲是x,所以在一开始初始化的时候每个数的父亲就是自己即自己就是树的根节点。
接下来就是进行查询,即查询所指定数所在树的根结点,具体代码如下
非递归版
如果儿子x不等于父亲f(x),那么就会把f(x)的值赋给x,即x向上移动,直到x移到根节点(根结点的父亲就是它自己)就会跳出循环,此时的x就是根结点。
递归版
递归版优化--路径压缩
不断向上递归,在返回的时候,所有的结点都连向根节点,将原来的树压缩成了一棵高度只有2的树,使后续的查询更加高效,因为直接让当前结点直接指向根节点。树的大致样子如下 :
接下来就是先按照题目将在一个集合中的数合并在一起 ,即合并树,具体代码如下
在分别查询到所需要查询的两个数的 根节点,将这两棵树合并,其实fx,fy谁做根结点都可以,可以写成f[fy]=fx;
现在来写一个完整代码来解决上面举的具体例子(假如要查询2,6是否在一个集合中,在就回答“YES”,否则回答“NO”)
public class UnionFindArray {
private int[] f;//f[i]=x,i的父亲是x
public UnionFindArray(int n) {
f = new int[n];
for(int i=0;i<n;i++){
f[i] = i;
}
}
public int find(int x){
if(f[x]!=x){
return f[x]=find(f[x]);
}
else{
return x;
}
}
public void union(int x,int y){
int fx=find(x);
int fy=find(y);
f[fx]=fy;
}
public static void main(String[] args) {
UnionFindArray uf = new UnionFindArray(7);
uf.union(1,2);
uf.union(3,5);
uf.union(1,6);
int f1=uf.find(2);
int f2=uf.find(6);
if(f1==f2){
System.out.println("YES");
}
else{
System.out.println("NO");
}
}
}
结果显示“YES”。因为1和2,1和6在同一个集合中,那么2和6在同一个集合中。
2.例题
这道题是力扣中的一道题,下面我们就结合上面将的并查集来解答。
首先要注意的一点是第i个城市于第j个城市相连,第j个城市于第i个城市相连是一个意思,即isConnected[i][j]=isConnected[j][i];
我们的思路就是先初始化,把每个城市都初始化为一棵树,然后把相连在一起的城市合并在一起,然后开始计数,统计有多少个根结点,即遍历所有数,判断它是否是根节点即是否满足f[x]==x,整体思路其实不难,具体代码如下
class Solution {
//f[i]=x,i的父亲是x
int []father =new int[200];
public int find(int x){
if(x!=father[x]){
return father[x]=find(father[x]);
}
else{
return x;
}
}
public void union(int x,int y){
int fx=find(x);
int fy=find(y);
if(fx!=fy){
father[fx]=fy;
}
}
public int findCircleNum(int[][] isConnected) {
int length=isConnected.length;
//初始化
for(int i=0;i<length;i++){
father[i]=i;
}
for(int i=0;i<length;i++){
for(int j=i+1;j<length;j++){
if(isConnected[i][j]==1){
union(i,j);
}
}
}
//统计结点个数
int count=0;
for(int i=0;i<length;i++){
if(father[i]==i)count++;
}
return count;
}
}