並查集學習筆記

前幾天看到一道聽說是小米的校招題,題目以下: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;
}
相關文章
相關標籤/搜索