本文参考链接1,链接2
最近刷力扣的每日一题连续遇到两道并查集的题,以前未接触过,这里做个总结。
并查集(UnionFind)主要解决动态连通性一类问题。
动态连通性问题的问题描述:输入一列整数对,其中每个整数都表示一个某种类型的对象,一对整数p,q被理解为相连的。
当从程序中读取整数对时,如果已知整数对都不能说明p,q是相连的,那么则将这一对整数对写入输出中,如果已知数据对可以说明p,q是相连的,那么就会略这一对p,q,读取下一对输入
动态连通性的应用场景:
- 网络连接判断:如果每个pair中的两个整数分别代表一个网络节点,那么该pair就是用来表示这两个节点是需要连通的。那么为所有的pairs建立了动态连通图后,就能够尽可能得减少布线的需要,因为已经连通的两个节点会被直接忽略掉。
- 变量名等同性(类似于指针的概念):
在程序中,可以声明多个引用来指向同一对象,这个时候就可以通过为程序中声明的引用和实际对象建立动态连通图来判断哪些引用实际上是指向同一对象。
什么时候用并查集:
对问题建模:
- 给出两个节点,判断它们是否连通,如果连通,不需要给出具体路径:并查集
- 给出两个节点,判断它们是否连通,如果连通,需要给出具体的路径:DFS
- 对于一个图,找出图中的不同连通分量,或者把一个连通分量中的点归到一个集合中:并查集。参考力扣第1202题。
思路:对于连通的所有节点,我们认为它们属于同一个组,不连通的节点属于不同的组。
步骤:
初始化:可以将所有节点以整数表示,在输入pairs之前,每个节点单独属于自己的组。如果每个节点用其他类型如字符串来表示,则需要用哈希表来映射,将String映射成Integer类型
操作:
- 查询节点属于的组
即parent[x],通过find方法来找父亲 - 判断两个节点是否属于同一个组
即判断parent[x]==parent[y] - 连接两个节点,使之属于同一个组
即合并操作,可以参考代码的find方法。一边查询一边修改结点指向是并查集的特色 - 获取组的数目
可以遍历一遍parent数组,返回parent[i]==i的个数。或初始化为节点的数目,每次成功连接两个节点之后,减1
并查集相关概念解释:
-
构建有向图
一般根据给出的pairs对进行构建,如pairs=[(x1,y1},(x2,y2)],可以通过令parent[x1]=y1, parent[x2]=y2来构建 -
路径压缩
为了避免并查集所表示的树形结构高度过高,影响查询性能,会对有向图(或树)进行路径压缩。路径压缩是是针对树的高度的优化。
路径压缩的效果是:在查询一个结点 a 的根结点同时,把结点 a 到根结点的沿途所有结点的父亲结点都指向根结点。特别的,根节点的父亲节点就是根节点自己。如下图,左图是原始的树,右图是路径压缩后的树。
路径压缩后的两棵树结构等价,树的高度为2,查询性能最好。
如下附上力扣399题中关于并查集的代码
//这里的weight可以根据情况而定,如果题目不需要权重,则可以去掉
//这是力扣的第399题中用的并查集方法的代码,在第547题中就不需要weight,只需要parent,在力扣的1202题中添加了rank[]数组,代表树的高度
private class UnionFind{
int[] parent;
double[] weight;
public UnionFind(int size){
parent = new int[size];
weight = new double[size];
for(int i = 0 ; i < size; i++){
parent[i]=i;
weight[i]=1.0d;
}
}
public void union(int x, int y, double value){
int RootX = find(x);
int RootY = find(y);
if(RootX != RootY) {
parent[RootX] = RootY;
weight[RootX] = weight[y] * value / weight[x];
}
}
//注意这里必须先记录原始父亲的id,然后递归找到正确的父亲,在递归过程中不断更新各级父亲的weight,最后再用更新后的原始父亲的weight来更新自己的weight
public int find(int x){
if(parent[x] != x){
int origin = parent[x];
parent[x] = find(parent[x]);
weight[x] *= weight[origin];
}
return parent[x];
}
public boolean isConnected(int x, int y){
int RootX = find(x);
int RootY = find(y);
if(RootX == RootY){
return true;
}
else {
return false;
}
}
}
这里写出目前我遇到的力扣题中用到并查集的题目:399,547,684,947,1202,以后遇到其他再补充
解题方法
1.为并查集添加weight成员变量,代表每个节点的权重,如399
2.为并查集添加rank成员变量,代表每个连通分量的高度,如1202
3.如上的代码中的parent是一维数组,如果题目中是二维,可以考虑对其中一维进行映射,将其中一维映射到不同的区间中,则横坐标和纵坐标就降维到了一维,然后进行一维的操作即可,如947