前几天看到一道听说是小米的校招题,题目以下:ios
假如已知有n我的和m对好友关系(存于数字r)。若是两我的是直接或间接的好友(好友的好友的好友...),则认为他们属于同一个朋友圈,请写程序求出这n我的里一共有多少个朋友圈。
假如:n = 5 , m = 3 , r = {{1 , 2} , {2 , 3} , {4 , 5}},表示有5我的,1和2是好友,2和3是好友,4和5是好友,则一、二、3属于一个朋友圈,四、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; } }
以后就是并查集的强项,搜索了,并查集的搜索很巧妙,用到一个叫路径压缩的思想,抽象树的所要子孙节点都指向了根节点。spa
int findset(int d){ if(d!=father[d]) father[d]=findset(father[d]); return father[d]; }
固然也有不用递归的方案,不过好像效率上没什么优点,并且递归显然比较容易看懂。
理解了并查集的存储,那合并就十分简单了,直接将要合并的子节点指向父节点就行了,这里为了表示层级关系,咱们引入rank数组。初始化都为零。code
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; }