並查集

以前就做過好多關於並查集的問題,但是時間長了不看又忘了,今天做課程設計大作業,瞟到有這麼一道簡單題,決心重新撿起來,並查集(Union-find Sets)是一種非常精巧而實用的數據結構,而這種方法的時間複雜度也是常數級的。
用我最喜歡的方式來說的話,像是分堆(專業一點的話就是集合)。
並查集的應用範圍也比較廣:(當然在問題中不會這麼直白的跟你說)
主要用於處理一些不相交集合的合併問題。一些常見的用途有求連通子圖、求最小生成樹的 Kruskal 算法和求最近公共祖先(Least Common Ancestors, LCA)等。
並查集的過程:我們把一些元素根據某些給定的關係分成若干集合,並在集合中選出代表元素。對於任意給定的一個元素我們可以快速的找到他所在的集合代表元素,並且能夠知道到底分了幾個集合,判斷給定兩個元素是否在一個集合裏面。這些看似簡單的問題,在實際應用中,都有着很重要的意義。比如暢通工程,查找還需建幾條路,能夠使得整個路途暢通,就是查找有幾個集合的問題。
並查集主要有三個函數
(圖是轉自網上的)
1.init()初始化
建立一個大集合(一個數組set[maxn]),把所有的元素都放進去,因爲此時我們還沒有給他們關係,因此他們每個都是一個集合,父節點都是自己本身。

void init(int n)
{
    for(int i=1;i<=n;i++)
    {
        set[i]=i;///初始化,把父節點都設爲自己本身
    }
}

這裏寫圖片描述
2.find()查找是否有關係
當操作開始的時候,在初始化中散落的元素們開始站隊了,他們根據題目中給出的關係開始站隊,我們怎麼看他們有沒有關係,就是通過find(),其實就是查找父節點(我們爲一個集合所設立的代表元素),這裏有一個優化方法,就是將所有的元素都直接連在父節點上,這樣可以省去查找所花費的時間,如圖所示:

int find(int x)///帶路徑壓縮的向上查找
{
    if(set[x]==x)
        return  x;
    return set[x]=find(set[x]);
}

(我個人比較喜歡遞歸版,效率沒什麼差別,但是看着比較簡潔)

這裏寫圖片描述
3.建立關係union(),合併
這就是給元素們站隊了,比如說a和b有關係了,其實就是將b的父節點指向a,讓他們建立一個聯繫,成爲一個集合,當兩個大一點的集合再合併的時候,就好像是兩大幫派由於某種利益關係合併成一個,這裏有一個優化就是按秩合併,在合併時,總是將具有較小秩的樹根指向具有較大秩的樹根。簡單的說,就是總是將比較矮的樹作爲子樹,添加到較高的樹中。這樣書高就不會增加很多,但是個人更喜歡路徑壓縮,路徑壓縮以後這種優化的效果也就不是太明顯了,而且這種優化的方式要寫的代碼也過於囉嗦了。

void union_set(int a,int b)
{
    int ta=find(a);
    int tb=find(b);
    if(ta==tb)
        return ;
    if(ta!=tb)
        set[ta]=set[tb];
}

這裏寫圖片描述
關於並查集的典型題目:
HDU1232 暢通工程
POJ 1611 The Suspects
POJ 1703 Find them,Catch them
POJ 1988 Cube Stacking
食物鏈
求連通子圖
求最小生成樹的 Kruskal 算法
求最近公共祖先LCA

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章