前幾天看到一道聽說是小米的校招題,題目以下: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; }