並查集 (Union-Find Sets)及其應用


並查集 (Union-Find Sets)及其應用


並查集:

(union-find sets)是一種簡單的用途廣泛的集合. 並查集是若干個不相交集合,能夠實現較快的合併和判斷元素所在集合的操作,應用很多。一般採取樹形結構來存儲並查集,並利用一個rank數組來存儲集合的深度下界,在查找操作時進行路徑壓縮使後續的查找操作加速。這樣優化實現的並查集,空間複雜度爲O(N),建立一個集合的時間複雜度爲O(1)N次合併M查找的時間複雜度爲O(M Alpha(N)),這裏AlphaAckerman函數的某個反函數,在很大的範圍內(人類目前觀測到的宇宙範圍估算有1080次方個原子,這小於前面所說的範圍)這個函數的值可以看成是不大於4的,所以並查集的操作可以看作是線性的。它支持以下三種操作:


  -Union (Root1, Root2) //並操作;把子集合Root2併入集合Root1.要求:Root1 Root2互不相交,否則不執行操作.
  -Find (x) //搜索操作;搜索元素x所在的集合,並返回該集合的名字
.
  -UFSets (s) //構造函數。將並查集中s個集合初始化爲s個只有一個單元素的子集合
.
  -對於並查集來說,每個集合用一棵樹表示。

  -集合中每個元素的元素名分別存放在樹的結點中,此外,樹的每一個結點還有一個指向其雙親結點的指針。
  -設 S1= {0, 6, 7, 8 }S2= { 1, 4, 9 }S3= { 2, 3, 5 }

    -爲簡化討論,忽略實際的集合名,僅用表示集合的樹的根來標識集合。
  -爲此,採用樹的雙親表示作爲集合存儲表示。集合元素的編號從0 n-1。其中 n 是最大元素個數。在雙親表示中,第 i 個數組元素代表包含集合元素 i 的樹結點。根結點的雙親爲-1,表示集合中的元素個數。爲了區別雙親指針信息( ≥ 0 ),集合元素個數信息用負數表示。

 

S1 ∪ S2的可能的表示方法:


void ufsets(int s)

{//構造函數

    int   i, parent[s]; 

    for(i = 0;  i < s; i++){

       parent[i] = -1;

    }

}

int find(int x)//搜索操作

{

    if(parent[x] <= 0){

          return x;

    }

    else{

       return find(parent[x]);

}

void union(int root1, int root2){//合併操作

    parent[root2] = root1;//root2指向root1

}

FindUnion操作性能不好。假設最初 n 個元素構成 n 棵樹組成的森林,parent[i] = -1。做處理Union(0, 1), Union(1, 2), …, Union(n-2, n-1)後,將產生如圖所示的退化的樹。

執行一次Union操作所需時間是O(1)n-1Union操作所需時間是O(n)。若再執行Find(0), Find(1), …, Find(n-1), 若被搜索的元素爲i,完成Find(i)操作需要時間爲O(i),完成 n 次搜索需要的總時間將達到

Union操作的加權規則

  爲避免產生退化的樹,改進方法是先判斷兩集合中元素的個數,如果以 i 爲根的樹中的結點個數少於以 j 爲根的樹中的結點個數,即parent[i] > parent[j],則讓 j 成爲 i 的雙親,否則,讓i成爲j的雙親。此即Union的加權規則

          parent[0](== -4) < parent[4] (== -3)

  void WeightedUnion(int Root1, int Root2) {
   //Union的加權規則改進的算法

   int temp = parent[Root1] + parent[Root2];
   
if ( parent[Root2] < parent[Root1] ) {
    parent[Root1] = Root2; //Root2中結點數多

    parent[Root2] = temp;  //Root1指向Root2
   }
   
else {
    parent[Root2] = Root1; //Root1中結點數多

    parent[Root1] = temp;  //Root2指向Root1
   }
  }

 

使用加權規則得到的樹

下面是幾到用並查集可以方便解決的問題:

題目: 親戚(Relations)

或許你並不知道,你的某個朋友是你的親戚。他可能是你的曾祖父的外公的女婿的外甥的表姐的孫子。如果能得到完整的家譜,判斷兩個人是否親戚應該是可行的,但如果兩個人的最近公共祖先與他們相隔好幾代,使得家譜十分龐大,那麼檢驗親戚關係實非人力所能及.在這種情況下,最好的幫手就是計算機。

爲了將問題簡化,你將得到一些親戚關係的信息,如同MarryTom是親戚,TomB en是親戚,等等。從這些信息中,你可以推出MarryBen是親戚。請寫一個程序,對於我們的關心的親戚關係的提問,以最快的速度給出答案。

 

參考輸入輸出格式 輸入由兩部分組成。

第一部分以NM開始。N爲問題涉及的人的個數(1 N 20000)。這些人的編號爲1,2,3,,N。下面有M(1 M 1000000),每行有兩個數ai, bi,表示已知aibi是親戚.

第二部分以Q開始。以下Q行有Q個詢問(1 Q 1 000 000),每行爲ci, di,表示詢問cidi是否爲親戚。

對於每個詢問ci, di,若cidi爲親戚,則輸出Yes,否則輸出No

 

樣例輸入與輸出

輸入relation.in

10 7

2 4

5 7

1 3

8 9

1 2

5 6

2 3

3

3 4

7 10

8 9

輸出relation.out

Yes

No

Yes

如果這道題目不用並查集,而只用鏈表或數組來存儲集合,那麼效率很低,肯定超時。

例程:

 

#include <stdio.h>

int n, m, q;
int pre[20000], rank[20000];

void makeset(int x)
{
        pre[x] = -1;
        rank[x] = 1;
}

void unionone(int a, int b)
{
        int root1 = find(a);
        int root2 = find(b);

        if(rank[root1] > rank[root2]){
                pre[root2] = root1;
                rank[root1]++;
        }
        else{
                pre[root1] = root2;
                rank[root2]++;
        }
}

int find(int x)
{
        int r = x, q;

        while(pre[r] != -1){
                r = pre[r];
        }
        return r;
}

int main(int argc, char **argv)
{
        int a, b, c, d, i;

        scanf("%d", &n);
        getchar();
        scanf("%d", &m);
        getchar();

        for(i = 0; i < n; i++){
                makeset(i);
        }

        for(i = 0; i < m; i++){
                scanf("%d %d", &a, &b);
                getchar();
                if(find(a) != find(b)){
                        unionone(a, b);
                }
        }

        scanf("%d", &q);
        getchar();

        for(i = 0; i < q; i++){
                scanf("%d %d", &c, &d);
                getchar();
                if(find(c) == find(d)){
                        printf("YES/n");
                }
                else{
                        printf("NO/n");
                }
        }

        return 0;
}

 

 原文地址 http://hi.baidu.com/zhanggmcn/blog/item/b8cbf963fcfdc4630d33fafb.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章