什么是并查集:
上次mentor和其他小伙伴开会,对对方说,啊,你用并查集去做啊,并查集
电话那头传来一阵沉默。什么是并查集?mentor急了 啊就是一种算法,一种算法。
对方。。。算法????????
并查集实际就是对问题进行分类。
举例子:好多羊群怎么管理,找出来每个羊群的领头羊就好了。
还有一个比喻很形象,武林外传里有很多门派,见面就要打打杀杀的,那么为了不自己打自己人,打架前都会报上自己门派老大的名字,来避免误伤。并查集也一样
并查集一共两块:
- 并:合并相同集合
- 查:查找父节点
注意并查集是1维的,当我们遇到二维的问题的时候要转化为1维去求解。
下面是两道比较经典的并查集问题:
岛屿问题:
首先转化为一维问题:
我的做法是将他们拉开 (x,y)-> x*ylen+y
第二步初始化并查集数组
第三步union,和find 这里记住要union 的是parent 指向的node,就是union find 到的节点
代码如下
class Solution {
public int count=0;
//find
public int find(int x,int[] parent){
while(parent[x]!=x)x=parent[x];
return x;
}
//union
public void union(int x,int y,int[] parent){
int p1=find(x,parent);
int p2=find(y,parent);
if(p1==p2){
return;
}
parent[p1]=p2;
this.count--;//one successful union like remove a node
}
//首先并查集是一维所以我们要把他转化为1维的 x*ylen+y
public int numIslands(char[][] grid) {
int xlen=grid.length;
if(xlen==0)return 0;
int ylen=grid[0].length;
int[] parent=new int[xlen*ylen];
//init let them point to themself
for(int i=0;i<xlen;i++){
for(int j=0;j<ylen;j++){
if(grid[i][j]=='1'){
parent[j+i*ylen]=j+i*ylen;
this.count++;
}
}
}
for(int i=0;i<xlen;i++){
for(int j=0;j<ylen;j++){
if(grid[i][j]=='1'){
//down
if(i+1<xlen&&grid[i+1][j]=='1'){
union(j+(i+1)*ylen,j+i*ylen,parent);
}
//right
if(j+1<ylen&&grid[i][j+1]=='1'){
union((j+1)+ylen*i,j+i*ylen,parent);
}
}
}
}
return count;
}
}
但是这个并不是最优的,因为可能查找的距离很长,所以一个优化就是设置rank
//union
public void union(int x,int y,int[] parent,int[] rank){
int p1=find(x,parent);
int p2=find(y,parent);
if(p1==p2){
return;
}
if(rank[p1]==rank[p2]){
rank[p1]++;
}
if(rank[p1]>rank[p2]){
parent[p2]=p1;
}else{
parent[p2]=p1;
}
this.count--;//one successful union like remove a node
}
为何会优化呢?
因为相当于让生成的树的高度尽量小了。就像扁平化管理一样。
具体看图:
2345的rank都是低于1的rank 故查找次数大大减小了。
Java 并查集解法:
这里是一个特殊的并查集
首先每个点对应的value是这个点和他存储的parent对应的倍数关系。
比如
a/b =5 b.value=5
c/d=3 c.value=3
b/d=2 这时候不会去更新d的数值,而是会去找d的parent c b的parent a 让这两个parent连接起来
相当于图的连接
那么就要求 a和c的关系
这里就涉及数学推导
a/b=v1//value.get(parent)
c/d=v2//value.get(child)
b/d=v3//value[i]
a=(v1*b)=v1*v3*d
c=v2*d
a/c=(v1*v3)/v2
所以 推导关系 出来了:
value.put(c,value[i]*value.get(parent)/value.get(child))
这个题的关键就这里了。代码如下:
class Solution {
//child parent
Map<String,String> parents=new HashMap<>();
//child mutiply of parent
Map<String,Double> values=new HashMap<>();
public void add(String x){
if(!parents.containsKey(x)){
parents.put(x,x);
values.put(x,1.0);
}
}
public void union(String parent,String child, double value){
add(parent);
add(child);
String p1=find(parent);
String p2=find(child);
if(p1==p2){
return;
}else{
parents.put(p2,p1);
values.put(p2,value*(values.get(parent)/values.get(child)));
}
}
public String find(String x){
while(parents.get(x)!=x){
x=parents.get(x);
}
return x;
}
public double cal(String x){
// System.out.println("cal x"+x);
double v=values.get(x);
while(parents.get(x)!=x){
x=parents.get(x);
v*=values.get(x);
}
return v;
}
public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
//union
for(int i=0;i<equations.size();i++){
//union parent child value
union(equations.get(i).get(0),equations.get(i).get(1),values[i]);
}
//find
double[] ans=new double[queries.size()];
for(int i=0;i<queries.size();i++){
String c1=queries.get(i).get(0);
String c2=queries.get(i).get(1);
if(!parents.containsKey(c1)||!parents.containsKey(c2)){
ans[i]=-1;
continue;
}
if(c1.equals(c2)){
ans[i]=1;
continue;
}
String p1=find(c1);
String p2=find(c2);
if(!p1.equals(p2)){
ans[i]=-1;
continue;
}
ans[i]=cal(c2)/cal(c1);
}
return ans;
}
}