一.概念
并查集,顾名思义,可以进行合并(并)和查找(查)的集合。我们用并查集可查找某一个元素是不是与另一个元素在同一个集合里,这些元素往往通过某一个规则合并在一起。
并查集一个集合就是一颗树,它是用树这种数据结构来实现的。
下图:123是一个集合,45是一个集合。
二.代码实现
1.定义
我们要一个数组去模拟树来存储点,每一个点存储的是其父节点的下标,如果一个点没有父节点是祖先节点,那么这个数记录的是负数,这个负数的绝对值是一整颗树的数量。
在定义数组时我们要注意数组的数量,看看数组的大小是不是会影响到程序功能的实现。
public int[] elem;
public UnionFindSet(int m){
this.elem=new int[m];
Arrays.fill(elem,-1);
}
2.找祖宗
从某一个节点向上走,找某一值是负数,那么这个值的下标就是要找的祖宗。
我们是用数组模拟树,不断通过存储的下标网上找即可。
public int findRoot(int x){
while(elem[x]>=0){
x=elem[x];
}
return x;
}
3.合并(并)
还是这个例子,我们要在3集合合并5集合,直观说就是将3和4连在一起,下图:
这个是连在一起后,数组的值:
代码上怎么实现?
找到5的祖宗节点,更新3的祖宗节点的值,让5的祖宗节点与3连接。
总结:1.找到两个要连接的点的祖宗节点;2.更新(x1)的祖宗节点的值;3.让其中一个节点(x2)的祖宗节点与另个节点(x1)连接。
注意,不能先连接,连接后的x2的祖宗节点的值会变化,无法完成x1的更新。
public void union(int x1,int x2){
if(isSameUnionFindSet(x1,x2)){
return ;
}
//1.找到两个要连接的点的祖宗节点
int root1=findRoot(x1);
int root2=findRoot(x2);
//2.更新(x1)的祖宗节点的值
elem[root1]=elem[root1]+elem[root2];
//3.让其中一个节点(x2)的祖宗节点与另个节点(x1)连接
elem[root2]=root1;
}
4.查找(查)
查找两个元素是不是一个集合,我们只需分别找到两个元素的祖宗节点,看看是不是同一个祖宗。
如果是,那么就这同一个集合;如果不是,那么就不在同一个集合。
代码实现很简单:
public boolean isSameUnionFindSet(int x1,int x2){
int root1=findRoot(x1);
int root2=findRoot(x2);
return root1==root2;
}
5.路径压缩
路径压缩,把路径给压缩了,可以减少查找祖宗节点的时间。
使用路径压缩时,对于数组的初始化不能是-1。如果是-1,那么将无法更新。
还是上面的例子,将2345的祖宗全部设成1:
下面是路径压缩的代码实现:
public int find(int x){
if(elem[x]!=x){
elem[x]=find(elem[x]);
}
return elem[x];
}
public void union(int x1,int x2){
elem[find(x1)]=find(x2);
}
三.例题
547. 省份数量
题目大意:给一些城市的连接关系,连在一起的就是同一个省份,求有多少个省份。
这个题就是典型的并查集问题,转换一下题目:求有多少个集合的个数。
这里我采用路径压缩的方法(代码简洁):
class Solution {
public int[] elem;
public int findCircleNum(int[][] isConnected) {
int n=isConnected.length;
elem=new int[n];
for(int i=0;i<n;i++){
elem[i]=i;
}
//合并
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(isConnected[i][j]==1){
//先判断在不在同一个集合
if(find(i)!=find(j)){
elem[find(i)]=find(j);
}
}
}
}
int sum=0;
for(int i=0;i<n;i++){
if(elem[i]==i){
sum++;
}
}
return sum;
}
public int find(int x){
if(elem[x]!=x){
elem[x]=find(elem[x]);
}
return elem[x];
}
}
小结:这个题用DFS和BFS做会更好,但这里介绍的是并查集做法。
990. 等式方程的可满足性
题目描述可自行去LeetCode查看。
思路很简单,利用集合的特性就行。先让==的全部放入一个集合,再去查找!=的是不是在同一个集合,如果是,那么就错了;如果不是,就对了。
代码实现:
class Solution {
public int[] elem;
public boolean equationsPossible(String[] equations) {
elem=new int[26];
for(int i=0;i<26;i++){
elem[i]=i;
}
for(int i=0;i<equations.length;i++){
if(equations[i].charAt(1)=='='){
int x1=equations[i].charAt(0)-'a';
int x2=equations[i].charAt(3)-'a';
elem[find(x1)]=find(x2);
}
}
for(int i=0;i<equations.length;i++){
if(equations[i].charAt(1)=='!'){
int x1=equations[i].charAt(0)-'a';
int x2=equations[i].charAt(3)-'a';
if(find(x1)==find(x2)){
return false;
}
}
}
return true;
}
public int find(int x){
if(elem[x]!=x){
elem[x]=find(elem[x]);
}
return elem[x];
}
}
例题后续还会更新,敬请期待!