並查集(UVA 1106)

來源:互聯網
上載者:User

標籤:style   blog   http   color   使用   os   strong   io   

POINT:

  把每個元素看成頂點,則一個簡單化合物就是一條無向邊,若存在環(即k對組合中有k種元素),則危險,不應該裝箱,反之,裝箱;

  用一個並查集維護連通分量集合,每次得到一種化合物(x, y)時檢查x, y是否在同一集合中,如果是,拒絕,反之接受。

 

並查集

  並查集是一種樹型的資料結構,用於處理一些不相交集合(Disjoint Sets)的合并及查詢問題。常常在使用中以森林來表示。

  集就是讓每個元素構成一個單元素的集合,也就是按一定順序將屬於同一組的元素所在的集合合并。

---------------------------------------------------------------------------------------------------------

以下轉自:http://www.cnblogs.com/cyjb/p/UnionFindSets.html,3Q~

---------------------------------------------------------------------------------------------------------

  並查集(Union-find Sets)是一種非常精巧而實用的資料結構,它主要用於處理一些不相交集合的合并問題。一些常見的用途有求連通子圖、求最小產生樹的 Kruskal 演算法和求最近公用祖先(Least Common Ancestors, LCA)等。

  使用並查集時,首先會存在一組不相交的動態集合 ,一般都會使用一個整數表示集合中的一個元素。

  每個集合可能包含一個或多個元素,並選出集合中的某個元素作為代表。每個集合中具體包含了哪些元素是不關心的,具體選擇哪個元素作為代表一般也是不關心的。我們關心的是,對於給定的元素,可以很快的找到這個元素所在的集合(的代表),以及合并兩個元素所在的集合,而且這些操作的時間複雜度都是常數級的。

並查集的基本操作有三個:

  1. makeSet(s):建立一個新的並查集,其中包含 s 個單元素集合。
  2. unionSet(x, y):把元素 x 和元素 y 所在的集合合并,要求 x 和 y 所在的集合不相交,如果相交則不合并。
  3. find(x):找到元素 x 所在的集合的代表,該操作也可以用於判斷兩個元素是否位於同一個集合,只要將它們各自的代表比較一下就可以了。

  並查集的實現原理也比較簡單,就是使用樹來表示集合,樹的每個節點就表示集合中的一個元素,樹根對應的元素就是該集合的代表, 1 所示。

                  

                      圖 1 並查集的樹表示

  圖中有兩棵樹,分別對應兩個集合,其中第一個集合為 {a, b, c, d}, 代表元素是a; 第二個集合為{e, f, g}, 代表元素是ee。

  樹的節點表示集合中的元素,指標表示指向父節點的指標,根節點的指標指向自己,表示其沒有父節點。沿著每個節點的父節點不斷向上尋找,最終就可以找到該樹的根節點,即該集合的代表元素。

  現在,應該可以很容易的寫出 makeSet 和 find 的代碼了,假設使用一個足夠長的數組來儲存樹節點(很類似之前講到的靜態鏈表),那麼 makeSet 要做的就是構造出 2 的森林,其中每個元素都是一個單元素集合,即父節點是其自身:

            

                    圖 2 構造並查集初始化

相應的代碼如下所示,時間複雜度是 0(n):

123456 const int MAXSIZE = 500;int uset[MAXSIZE]; void makeSet(int size) {    for(int i = 0;i < size;i++) uset[i] = i;}

  接下來,就是 find 操作了,如果每次都沿著父節點向上尋找,那時間複雜度就是樹的高度,完全不可能達到常數級。這裡需要應用一種非常簡單而有效策略——路徑壓縮。

  路徑壓縮,就是在每次尋找時,令尋找路徑上的每個節點都直接指向根節點, 3 所示。

      

                       圖 3 路徑壓縮

我準備了兩個版本的 find 操作實現,分別是遞迴版和非遞迴版,不過兩個版本目前並沒有發現有什麼明顯的效率差距,所以具體使用哪個完全憑個人喜好了。

12345678910 int find(int x) {    if (x != uset[x]) uset[x] = find(uset[x]);    return uset[x];}int find(int x) {    int p = x, t;    while (uset[p] != p) p = uset[p];    while (x != p) { t = uset[x]; uset[x] = p; x = t; }    return x;}

最後是合併作業 unionSet,並查集的合并也非常簡單,就是將一個集合的樹根指向另一個集合的樹根, 4 所示。

        

                      圖 4 並查集的合并

這裡也可以應用一個簡單的啟發學習法策略——按秩合并。該方法使用秩來表示樹高度的上界,在合并時,總是將具有較小秩的樹根指向具有較大秩的樹根。簡單的說,就是總是將比較矮的樹作為子樹,添加到較高的樹中。為了儲存秩,需要額外使用一個與 uset 同長度的數組,並將所有元素都初始化為 0。

12345678 void unionSet(int x, int y) {    if ((x = find(x)) == (y = find(y))) return;    if (rank[x] > rank[y]) uset[y] = x;    else {        uset[x] = y;        if (rank[x] == rank[y]) rank[y]++;    }}

下面是按秩合并的並查集的完整代碼,這裡只包含了遞迴的 find 操作。

const int MAXSIZE = 500;int uset[MAXSIZE];int rank[MAXSIZE]; void makeSet(int size) {    for(int i = 0;i < size;i++)  uset[i] = i;    for(int i = 0;i < size;i++)  rank[i] = 0;}int find(int x) {    if (x != uset[x]) uset[x] = find(uset[x]);    return uset[x];}void unionSet(int x, int y) {    if ((x = find(x)) == (y = find(y))) return;    if (rank[x] > rank[y]) uset[y] = x;    else {        uset[x] = y;        if (rank[x] == rank[y]) rank[y]++;    }}

除了按秩合并,並查集還有一種常見的策略,就是按集合中包含的元素個數(或者說樹中的節點數)合并,將包含節點較少的樹根,指向包含節點較多的樹根。這個策略與按秩合并的策略類似,同樣可以提升並查集的運行速度,而且省去了額外的 rank 數組。

這樣的並查集具有一個略微不同的定義,即若 uset 的值是正數,則表示該元素的父節點(的索引);若是負數,則表示該元素是所在集合的代表(即樹根),而且值的相反數即為集合中的元素個數。相應的代碼如下所示,同樣包含遞迴和非遞迴的 find 操作:

const int MAXSIZE = 500;int uset[MAXSIZE]; void makeSet(int size) {    for(int i = 0;i < size;i++) uset[i] = -1;}int find(int x) {    if (uset[x] < 0) return x;    uset[x] = find(uset[x]);    return uset[x];}int find(int x) {    int p = x, t;    while (uset[p] >= 0) p = uset[p];    while (x != p) {        t = uset[x];        uset[x] = p;        x = t;    }    return x;}void unionSet(int x, int y) {    if ((x = find(x)) == (y = find(y))) return;    if (uset[x] < uset[y]) {        uset[x] += uset[y];        uset[y] = x;    } else {        uset[y] += uset[x];        uset[x] = y;    }}

如果要擷取某個元素 x 所在集合包含的元素個數,可以使用 -uset[find(x)] 得到。

並查集的空間複雜度是O(n)的,這個很顯然,如果是按秩合并的,占的空間要多一些。find 和 unionSet 操作都可以看成是常數級的,或者準確來說,在一個包含 n 個元素的並查集中,進行 m 次尋找或合併作業,最壞情況下所需的時間為 O(mα(n)),這裡的α是 Ackerman 函數的某個反函數,在極大的範圍內(比可觀察到的宇宙中估計的原子數量 10^80 還大很多)都可以認為是不大於 4 的。具體的時間複雜度分析,請參見《演算法導論》的 21.4 節 帶路徑壓縮的按秩合并的分析。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.