并查集及变形

基础并查集

        并查集可以进行集合归并操作,并能方便的查询两个元素是否在同一集合,原理是查询每个元素的根节点,如果两个元素根节点相同,那么两个元素处于同一个集合。关键是代码量少,这么简单实用的数据结构当然要好好研究下。

1.初始化。初始化使每个元素的根节点指向自己,自己是一个独立的集合。一般通过数组存储每个元素的父节点。
在这里插入图片描述

 constructor(n){
     this.fa = new Array(n+1);
     for(let i=0;i<=n;i++) this.fa[i] = i;
 }

2.查找元素根节点操作。该操作是不断递归查询节点的父节点,直到集合的根节点。怎么判断根节点呢,当元素的父节点和下标相同就是根节点了。比如我们进行了合并1和3节点,3和5节点,结果如图:
在这里插入图片描述
我们查询1的节点的父节点,指向3,发现3也不是根节点,接着找到了5,5的父节点和下标相同就是集合的根节点。可得知3和5的根节点相同,也是同一个集合的。

find(x){
//this.fa[x] = this.find(this.fa[x])主要是优化树的高度,比如优化后1的根节点就直接是5了,后面查询就更快。该操作的复杂度是逆阿克曼函数,只比O(1)慢一点。
  return (this.fa[x]===x)?x:(this.fa[x] = this.find(this.fa[x]));
}

3.集合合并,集合合并是通过改变该点指向的父节点,从而改变该点指向的集合。
合并节点1和2,3和4后如图:
在这里插入图片描述
一般合并的话都是找到根节点合并的,比如上图1和2已经处于同一个集合,假如1还要和3合并那么2和3也一定是同一个集合的。所以需要找到根节点合并两个集合。

join(a,b){//合并两个元素
    a = this.find(a);
    b = this.find(b);
    this.fa[a] = b;
}

完整的基础版代码:

class DisjointSet{
    constructor(n){//初始化
        this.fa = new Array(n+1);
        for(let i=0;i<=n;i++) this.fa[i] = i;
        this.count = n;//集合的个数
    }
    find(x){//查询x的根节点
        return (this.fa[x]===x)?x:(this.fa[x] = this.find(this.fa[x]));
    }
    join(a,b){//合并两个元素
        a = this.find(a);
        b = this.find(b);
        if(a!==b){//不在同一集合
          this.count--;
          this.fa[a] = b;
        }
    }
    samecollect(a,b){//两个元素是否同一个集合
        return this.find(a)===this.find(b);
    }
}

可以尝试一下:547. 省份数量

带权并查集

可能遇到这样的场景,元素之间有倍数关系,且倍数关系可以传递。我们最后要查询任意两个元素间的关系。这时候就可以使用带权重的并查集,我们加上一个表示元素间权重的数组,该数组存放和父节点的倍数关系,父节点是谁?当然是和fa数组同步的。最后查询两个节点后也可以方便计算出两个节点的倍数关系。

class DisJointUnit{
    constructor(n){
        this.fa = new Array(n);
        this.weight = new Array(n);
        for(let i=0;i<n;i++){
            this.fa[i] = i;
            this.weight[i] = 1;//父节点是自身为1倍关系
        }
        this.count = n;
    }
    union(x,y,val){//节点合并
        let a = this.find(x);
        let b = this.find(y);
        if(a!=b){
            this.count--;
            this.fa[a] = b;
            this.weight[a] = val*this.weight[y]/this.weight[x];//更新节点的倍数关系
        }
    }
    find(x){//寻找某节点的根
        if(x===this.fa[x]) return x;
        else{
            let next = this.find(this.fa[x]);
            this.weight[x] = this.weight[x]*this.weight[this.fa[x]];
            this.fa[x] = next;//对fa数组操作也要更新weight数组的倍数关系
        }
        return this.fa[x];
    }
}

可以尝试一下: 399. 除法求值

带查询集合大小并查集

有时候我们不但需要知道元素间的关系,还要知道元素所在集合的大小,里面有多少元素。同样需要一个数组表示元素所在集合的大小,而集合大小只有集合合并才会改变,比带权的要简单。

class DisjointSet{
    constructor(n){
        this.fa = new Array(n);
        this.size = new Array(n).fill(1);//保存各个集合的大小
        this.maxSize = 1;
        for(let i=0;i<n;i++) this.fa[i] = i;
    }
    union(a,b){
        let ax = this.find(a);
        let bx = this.find(b);
        if(ax!==bx){
            this.fa[bx] = ax;
            this.size[ax]+=this.size[bx];
            this.maxSize = Math.max(this.size[ax]);
        }
    }
    getSize(x){//获取某个集合的大小
        return this.size[this.find(x)];
    }
    find(x){
        return this.fa[x] === x?x:(this.fa[x] = this.find(this.fa[x]));
    }
    isConnect(a,b){
        return this.find(a)===this.find(b);
    }
}

可以尝试一下: 827. 最大人工岛

反向点并查集

有时候我们不知道两个点是否同一个集合,但是知道两个点不在同一个集合,那么这个时候可以用并查集吗。当然也可以,我们需要要构造反向的点将互斥的两个元素分别在反向点连接。反向点只要把并查集的范围扩大一倍,n+x就是x的反向点了。
如题:886. 可能的二分法
比如有一组互斥点:[1,2] [2,3] [1,3]
怎么判断能不能分为两组,用并查集的方法,处理前两组后如图:
在这里插入图片描述

这个时候我们发现1和3在同一个集合,不能互斥也就无法分为两组。

这些就是本人做题过程中遇到的一些并查集比较方便解决的问题,如果有更多的适用场景欢迎指教。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值