算法基础 - 并查集

并查集

在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。

问题举例

例如:
给你一堆人的名字,叫:a , b, c, d, e, f, g然后呢,告诉你们这些人是有亲戚关系的,每次只告诉你两个人之间是有亲戚关系的,然后给两个人,询问他们是不是有亲戚关系。

解答:
最简单的办法是,最开始每个人都是独立的,每个人都是一个集合,{a}, {b}, {c}, {d}, {e}这样子,然后假如告诉你说a,b两个人是有亲戚的关系,那么就把这两个人放入一个集合里。变成{a,b},这样他们在查询的时候就知道有亲戚关系了。
假如有{a,b}, {c,d}两个集合,然后告诉你b,c有亲戚关系,就要变成{a, b, c, d}这样四个人就都有亲戚关系了。

那么如何表示呢? 其实假如这些人之间是有长辈关系的话,就比较容易明白了,例如:b是a的长辈,c是b的长辈,c是d的长辈:那么就记成:parent[a] = b, parent[b] = c, parent[d] = c这里的parent就是一个映射的数组。

搜索的时候就搜索他们的最上面的长辈就好了,因为a -> b -> c那么a的长辈就是c。而d -> c那么d的长辈也是c,那么假如问a和d的话,他们两个就是有亲戚了。

集合表示

这里只记录每个人的第一个长辈是谁,就是map[a] = b这样子来记。假如是亲戚,并没有说谁是长辈,那么表示的时候,就需要注意了。例如:a b是亲戚关系,那么记录map[a] = b,假如a c也是亲戚关系,再记录map[a] = c就会出现覆盖的情况,这个时候要知道,这是一个链式结构,这个链式结构是有一个头的,这个头指向的是自己,所以每次只要用链表的头部就可以了。

代码表示

#include <iostream>
#include <unordered_map>
#include <string>
#include <cstring>

using namespace std;
unordered_map<string, string> map;

string find_parent(string name){
    if(map.find(name) == map.end()){
        return name;
    }
    if(name == map[name]){
        return map[name];
    }
    map[name] = find_parent(map[name]); //这里进行状态压缩了
    return map[name];
}


int main(){
    int T;
    cin>>T;
    while(T--){
        int s;
        string name_1, name_2;
        cin>>s;
        cin>>name_1>>name_2;
        if(s == 0){
            name_1 = find_parent(name_1);
            name_2 = find_parent(name_2);
            if(name_1 == name_2){
                continue;
            }
            map[name_1] = name_2;
        }else{
            if(find_parent(name_1) == find_parent(name_2)){
                cout<<"yes"<<endl;
            }else{
                cout<<"no"<<endl;
            }
        }
    }

    return 0;
}

状态压缩

所谓的状态压缩就是,假如我们是a -> b -> c -> d的话,那么我们找到a的最祖先的节点就是d,也就是需要寻找四次,如果两个都是要寻找四次,那么判断两个节点是不是在一个祖先的时候,就需要8次操作,然后我们进行状态压缩,也就是把上面的链式结构,变成多个子节点指向父节点的结构,a -> d, b -> d, c->d d->d这样每次查找的时候就可以一次就查找到了。

假如不状态压缩的话:

string find_parent(string name){
    if(map.find(name) == map.end()){
        return name;
    }
    if(name == map[name]){
        return map[name];
    }
    return find_parent(map[name]);
}

如果进行状态压缩的话就要把当前节点,指向自己的祖宗。

string find_parent(string name){
    if(map.find(name) == map.end()){
        return name;
    }
    if(name == map[name]){
        return map[name];
    }
    map[name] = find_parent(map[name]); //这里状态压缩
    return map[name];
}

以上。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值