并查集学习笔记

前几天看到一道据说是小米的校招题,题目如下:

假如已知有n个人和m对好友关系(存于数字r)。如果两个人是直接或间接的好友(好友的好友的好友...),则认为他们属于同一个朋友圈,请写程序求出这n个人里一共有多少个朋友圈。
假如:n = 5 , m = 3 , r = {{1 , 2} , {2 , 3} , {4 , 5}},表示有5个人,1和2是好友,2和3是好友,4和5是好友,则1、2、3属于一个朋友圈,4、5属于另一个朋友圈,结果为2个朋友圈。
输入:
输入包含多个测试用例,每个测试用例的第一行包含两个正整数 n、m,1= 输出:
对应每个测试用例,输出在这n个人里一共有多少个朋友圈。
样例输入:
5 3
1 2
2 3
4 5
3 3
1 2
1 3
2 3
0
样例输出:
2
1

乍一看去,似乎很简单,但仔细做的时候却在数据结构中有点迷糊,究竟什么数据结构来模拟呢?我抓狂了一晚上都不完善,后来搜一下才发现这是一道典型的并查集问题,顿时感觉读书少了。仔细看了一下,这确实是个很经典的数据结构,也很巧妙。
首先,并查集是用来干什么的呢?并查集是解决不相交的元素合并查询的问题的,这类问题看似简单,但因为要反复查找元素所在的集合,数据量极大,抽象成并查集解决起来非常方便。

并查集类似于森林,用数组来描述。注意,数组里保存的值指向父元素节点。开始时所有的元素都指向自身,并查集初始化十分简单:

void make(int size){
            int i;
            for(i=0;i<size;i++){
                    father[i]=i;
             }
}

之后就是并查集的强项,搜索了,并查集的搜索很巧妙,用到一个叫路径压缩的思想,抽象树的所要子孙节点都指向了根节点。

int findset(int d){
    if(d!=father[d])
            father[d]=findset(father[d]);
    return father[d];
}

当然也有不用递归的方案,不过好像效率上没什么优势,而且递归显然比较容易看懂。
理解了并查集的存储,那合并就十分简单了,直接将要合并的子节点指向父节点就好了,这里为了表示层级关系,我们引入rank数组。初始化都为零。

void unionSet(int a,int b){
    a=findset(a);
     b=findset(b);
    if(a==b)
            return;
    if(rank[a]>rank[b]){
             father[b]=a;
    }else{
              father[a]=b;
             if(rank[a]==rank[b]){
               rank[b]++;
            }
    }
}

呃,绕这么半天,这道题怎么解呢:

#include<iostream>
using namespace std;

int father[100010];
int findSet(int x){
    if(father[x]!=x)
    father[x]=findSet(father[x]);
return father[x];
}
void unionSet(int a,int b){
   int fa=findSet(a);
    int fb=findSet(b);
    if(fa==fb)
        return;
    father[fa]=fb;
}
int main(){
    int i,n,m,a,b;
    while(cin>>n){
        if(n==0) return 0;
        cin>>m;
        for(i=1;i<=n;i++) father[i]=i;
            for(i=0;i<m;i++){
                    cin>>a>>b;
                    unionSet(a,b);
            }
        int sum=0;
         for(i=1;i<=n;i++){
        if(father[i]==i)  sum++;
    }
    cout<<sum<<endl;
}
return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值