并查集c++代码_并查集经典-岛屿问题-除法问题

什么是并查集:

上次mentor和其他小伙伴开会,对对方说,啊,你用并查集去做啊,并查集

电话那头传来一阵沉默。什么是并查集?mentor急了 啊就是一种算法,一种算法。

对方。。。算法????????

并查集实际就是对问题进行分类。

举例子:好多羊群怎么管理,找出来每个羊群的领头羊就好了。

还有一个比喻很形象,武林外传里有很多门派,见面就要打打杀杀的,那么为了不自己打自己人,打架前都会报上自己门派老大的名字,来避免误伤。并查集也一样

并查集一共两块:

  1. 并:合并相同集合
  2. 查:查找父节点

注意并查集是1维的,当我们遇到二维的问题的时候要转化为1维去求解。

下面是两道比较经典的并查集问题:

岛屿问题:

94755723598ca6d8921720897d495ee6.png

首先转化为一维问题:

我的做法是将他们拉开 (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
    }

为何会优化呢?

因为相当于让生成的树的高度尽量小了。就像扁平化管理一样。

具体看图:

b03327534145653d138ac2d16d4bc5e5.png

2345的rank都是低于1的rank 故查找次数大大减小了。

6478d80d73f63a4de2ad2b7c01a253da.png

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连接起来
相当于图的连接

5a8ba5302eaa68c3f795056236834fae.png


那么就要求 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;


    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值