數據結構 -- 並查集

        時間過得真快,一眨眼集訓第二週就要結束了。有很長的第一段時間沒有寫算法類的博客了。今天先來一篇 --- 並查集。

1、什麼是並查集

       並查集(Union Find)是一種用於管理分組的數據結構。它具備兩個操作:(1)查詢元素a和元素b是否爲同一組   (2) 將元素a和b合併爲同一組。

注意:並查集不能將在同一組的元素拆分爲兩組。

2、並查集的結構

      並查集可以使用樹來實現。

       使用樹形結構來表示以後,每一組都對應一棵樹,然而我們就可以將這個問題轉化爲樹的問題了,我們看兩個元素是否爲一組我們只要看這兩個元素的根是否一致。顯然,使用樹形結構將問題簡單化了。合併時是我們只需要將一組的根與另一組的根相連即可。

3、並查集的實現

       我們通過以上的思路,現在就可以具體來實現並查集了,代碼如下:

int node[i]; //每個節點

//初始化n個節點
void Init(int n){
    for(int i = 0; i < n; i++){
        node[i] = i;
    }
}
//查找當前元素所在樹的根節點(代表元素)
int find(int x){
    if(x == node[x])
        return x;
    return find(node[x]);
}
//合併元素x, y所處的集合
void Unite(int x, int y){
    //查找到x,y的根節點
    x = find(x);
    y = find(y);
    if(x == y) 
        return ;
    //將x的根節點與y的根節點相連
    node[x] = y;
}
//判斷x,y是屬於同一個集合
bool same(int x, int y){
    return find(x) == find(y);
}

       有了這些知識,我們就可以來解決一些問題了。先上兩道並查集的水題:暢通工程How many Tables?

4、並查集的優化

        細心的同學會發現,我們使用以上的代碼來實現並查集,會出現一個問題  ---  樹的退化。在樹形數據結構中,如果發生退化,那麼複雜度將會變得很高(最慘的是直接退化成線性結構)。因此我們必須設法,阻止這種退化。爲此我們這裏有兩種方法。

<1>對於每一棵樹,我們記錄它的高度(rank)。在每次合併操作時,將高度小的樹 放在 高度高的樹下,成爲它的子樹:

      通過這樣處理之後,可以有效的防止樹的退化,但是查詢效率還不夠快,於是我們有提出了第二種優化方案。

<2>路徑壓縮

      我們將原先間接與根相連的節點,讓它與根直連,這樣效率又可以大大的提高了:

      我們在第一次查詢時,進行這步操作,當再次查詢時,就大大提高了效率。

5、並查集的複雜度

      加入這兩個優化之後,並查集的效率就非常高。對n個元素的並查集操作一次的複雜度是: O(α(n))。這裏,α(n)是阿克曼(Ackermann)函數的反函數。效率要高於O(log n)。

不過這裏O(α(n))是平均複雜度。也就是說,多次操作之後平均複雜度爲O(α(n)),換而言之,並不是每一次操作都滿足O(α(n))。

6、優化後的並查集實現

int node[i]; //每個節點
int rank[i]; //樹的高度

//初始化n個節點
void Init(int n){
    for(int i = 0; i < n; i++){
        node[i] = i;
        rank[i] = 0;
    }
}
//查找當前元素所在樹的根節點(代表元素)
int find(int x){
    if(x == node[x])
        return x;
    return node[x] = find(node[x]); //在第一次查找時,將節點直連到根節點
}
//合併元素x, y所處的集合
void Unite(int x, int y){
    //查找到x,y的根節點
    x = find(x);
    y = find(y);
    if(x == y) 
        return ;
    //判斷兩棵樹的高度,然後在決定誰爲子樹
    if(rank[x] < rank[y]){
        node[x] = y;
    }else{
        node[y] = x;
        if(rank[x] == rank[y]) rank[x]++:
    }
}
//判斷x,y是屬於同一個集合
bool same(int x, int y){
    return find(x) == find(y);
}

      現在我們已經大致的來解了並查集,我們來一道,有一點難度的題目練練手:食物鏈


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