【学习笔记】并查集

核心思想:通过“合并”和“查找”两种操作来动态维护集合元素之间的关系。

适用问题:具有 传递性 和 对称性 的二元关系的结点,这类问题可以抽象为不同集合之间的问题

传递性:

对称性:

问题转化:

抽象成集合之间的问题之后,原问题就进行了转化:

  1. 判断元素之间是否有联系 → 判断两个元素是否属于同一集合

  1. 两个元素建立联系 → 将两个元素所属集合合并

实现方式:常用数组实现,其他方法亦可

基本实现:

  1. 集合初始化:创建一个数组用于存储该结点所在集合的代表结点(也可以说是根结点),所有该集合的结点都指向他。一开始默认都指向自己,各自是独立的集合。

//创建存储该结点所在集合的代表结点数组
int father[N];

int init(int n){
    for(int i=1;i<=n;i++)
        //一开始默认都指向自己,各自是独立的集合。
        father[i] = i;
}
  1. 合并集合:将两个集合进行合并。将其中一个集合的代表结点指向另一个结合的代表结点。

注意:由于两个集合里的结点都指向各自的代表结点,而当前进行关联的两个结点并不一定是各自的代表结点,如果直接将其中一个指向另一个,并不能将两个集合合并,只是该结点与另一集合合并,从而造成错误。

void unionn(int x,int y){
    //将其中一个集合的代表结点指向另一个结合的代表结点。
    father[find(x)] = find(y);
}
  1. 查找当前集合的代表结点:查找当前结点所处集合的代表结点。由于可能存在链状结构,即代表结点是当前结点的祖先结点,而非直接父结点,所以需要迭代找出代表结点。

//非递归形式
int find(int x){
    while(father[x] != x) x = father[x];
    return x;
}

//递归形式
int find(int x){
    if (father[x] != x) return find(father[x]);
    return x;
}

优化:路径压缩

如上边所说,有可能会形成链状结构,这样在查找某一集合的代表结点时会很耗时,所以可以进行优化,避免产生过长的链状结构。

基本思路:查找过程中顺带将沿途的全部结点一起指向代表结点。

int find(int x){
    if (father[x] != x) father[x] = find(father[x]);
    return father[x];
}

集合数量的求解:

基本思路:每个集合的代表结点是唯一的,即 代表结点数 = 集合数。而代表结点的特征是指向自己,所以只需要数一下指向自己的结点数即可。

int getSetCnt(int n){
    int cnt = 0;
    for(int i=1;i<=n;i++){
        if(father[i] == i) cnt++;
    }
    return cnt;
}

各集合内结点个数求解:

基本思路:创建新的数组专门存储集合的结点个数。假设初始状态下,每个结点是单独的集合,值都为1。建立全部联系后,根据集合关系,将数值分别加入到各自集合的代表结点上。

int count1[N];

int init(int n){
    for(int i=1;i<=n;i++){
        //一开始默认都指向自己,各自是独立的集合。
        father[i] = i;
        //一开始各自是单独结合,所以集合个数为1。
        count1[i] = 1;
}

void getNodeCntPerSet(int n){
    for(int i=1;i<=n;i++){
        //将数值分别加入到各自集合的代表结点上
        if (father[i] != i)  count1[find(i)] += count1[i], count1[i] = 0;
    }
}

参考资料:

[1]《信息学奥赛一本通(c++版)》第五版,董永建,科学技术文献出版社,p496-507

[2]https://blog.csdn.net/u011575841/article/details/78992099

[3]https://blog.csdn.net/wangwei6125/article/details/68954171

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值