**)
並查集的作用:並和查,即合併和查詢,將一些集合合併,快速查詢或判斷某兩個集合的關係,或某元素與集合的關係,或某兩個元素的關係。
並查集的結構:並查集主要操作物件是森林,樹的結構賦予它獨特的能力,對整個集合操作轉換為對根節點(或稱該集合的代表元素)的操作,乙個集合裡的元素關係不一定確定,但相對於根節點的關係很明了,這也是為了查詢方便。
並查集優化方法:按秩合併和路徑壓縮的配合使用,使得查詢過程優化到極致。按秩合併,每次將深度小的樹合併到深度大的樹裡面去,使得整棵樹盡量矮;路徑壓縮,將當前節點到根節點路徑上的所有點直接連到根節點上,使得每個點到根節點的距離更短,在下一次查詢的時候更快。
如何快速確定偏移量公式:
例:現在要合併節點x,y, 找到根節點fx = find(x); fy = find(y);一般情況下,根節點的偏移量都保持為0, offset[foot] = 0;如果要使得x和y的偏移量為t,假設fx指向fy,則可以寫出公式offset[x] + offset[fx] - offset[y] = t,則offset[fx] = (offset[y] + t - offset[x]) % n; 這個n即為總共有多少類,如:在poj1182 食物鏈中n = 3,,在poj2492 a bug's life中n = 2, 這樣fx的偏移量就計算出來了,只需要改其中乙個根節點的偏移量,這裡是fx,因為假設是fx指向fy。
非遞迴路勁壓縮:
1**2view code3int find(intx)8
int y =x;
9while (y !=bin[y])
13return
r;
14 }
遞迴式路徑壓縮:
1view codeint find(intx)5
return
x;6 }
部分並查集題:
poj1611 the suspects
題解poj2492 a bug's life
題解poj1182 食物鏈
題解hdu1558 segment set
題解hdu1198 farm irrigation
題解三、並查演算法
通過對上面引題的分析,我們已經十分清楚——所謂並查集演算法就是對不相交集合(disjoint set)進行如下兩種操作:
(1)檢索某元素屬於哪個集合;
(2)合併兩個集合。
我們最常用的資料結構是並查集的森林實現。也就是說,在森林中,每棵樹代表乙個集合,用樹根來標識乙個集合。有關樹的形態在並查集中並不重要,重要的是每棵樹里有那些元素。
1. 合併操作
為了把兩個集合s1和s2並起來,只需要把s1的根的父親設定為s2的根(或把s2的根的父親設定為s1的根)就可以了。
這裡有乙個優化:讓深度較小的樹成為深度較大的樹的子樹,這樣查詢的次數就會少些。這個優化稱為啟發式合併。可以證明:這樣做以後樹的深度為o(logn)。即:在乙個有n個元素的集合,我們將保證移動不超過logn次就可以找到目標。
【證明】我們合併乙個有i個結點的集合和乙個有j個結點的集合,我們設i≤j,我們在乙個小的集合中增加乙個被跟隨的指標,但是他們現在在乙個數量為i+j的集合中。由於:
1+log i=log(i+i)<=log(i+j);
所以我們可以保證性質。
由於使用啟發式合併演算法以後樹的深度為o(logn),因此我們可以得出如下性質:啟發式合併最多移動2logn次指標就可以決定兩個事物是否想聯絡。
同時我們還可以得出另乙個性質:啟發式快速合併所得到的集合樹,其深度不超過 ,其中n是集合s中的所有子集所含的成員數的總和。
【證明】我們可以用歸納法證明:
當i=1時,樹中只有乙個根節點,即深度為1
又|log2 1|+1=1所以正確。
假設i≤n-1時成立,嘗試證明i=n時成立。
不失一般性,可以假設此樹是由含有m(1≤m≤n/2)個元素,根為j的樹sj,和含有n-m個元素、根為k的樹sk合併而得到,並且,樹j合併到樹k,根是k。
(1)若合併前:子樹sj的深度<子樹sk的深度
則合併後的樹深度和sk相同,深度不超過:
|log2(n-m)|+1
顯然不超過|log2 n|+1;
(2)若合併前:子樹sj的深度≥子樹sk的深度
則合併後的樹的深度為sj的深度+1,即:
(|log2m|+1)+1=|log2(2m)|+1<=|log2n|+1
2. 查詢操作
查詢乙個元素u也很簡單,只需要順著葉子到根結點的路徑找到u所在的根結點,也就是確定了u所在的集合。
這裡又有乙個優化:找到u所在樹的根v以後,把從u到v的路徑上所有點的父親都設定為v,這樣也會減少查詢次數。這個優化稱作路徑壓縮(compresses paths)。
壓縮路徑可以有很多種方法,這裡介紹兩種最常用的方法:
(1)滿路徑壓縮(full compresses paths):這是一種極其簡單但又很常用的方法。就是在新增另乙個集合的時候,把所有遇到的結點都指向根節點。
(2)二分壓縮路徑(compresses paths by halving):具體思想就是把當前的結點,跳過乙個指向父親的父親,從6而使整個路徑減半深度減半。這種辦法比滿路徑壓縮要快那麼一點點。資料越大,當然區別就會越明顯。
壓縮路徑的本質使路徑深度更加地減小,從而使訪問的時候速度增快,是一種很不錯的優化。在使用路徑壓縮以後,由於深度經常性發生變化,因此我們不再使用深度作為合併操作的啟發式函式值,而是使用乙個新的rank數。剛建立的新集合的rank為0,以後當兩個rank相同的樹合併時,隨便選一棵樹作為新根,並把它的rank加1;否則rank大的樹作為新根,兩棵樹的rank均不變。
3. 時間複雜度
並查集進行n次查詢的時間複雜度是o(n )(執行n-1次合併和m≥n次查詢)。其中 是乙個增長極其緩慢的函式,它是阿克曼函式(ackermann function)的某個反函式。它可以看作是小於5的。所以可以認為並查集的時間複雜度幾乎是線性的。
通過上面的分析,我們可以得出:並查集適用於所有集合的合併與查詢的操作,進一步還可以延伸到一些圖論中判斷兩個元素是否屬於同乙個連通塊時的操作。由於使用啟發式合併和路徑壓縮技術,可以講並查集的時間複雜度近似的看作o(1),空間複雜度是o(n),這樣就將乙個大規模的問題轉變成空間極小、速度極快的簡單操作。
並查集模板
1view code1、make_set(x) 把每乙個元素初始化為乙個集合
2建立乙個新的集合,其中集合只有唯一的乙個元素x34
2、union_set(x, y) 按秩合併x,y所在的集合56
3、find_set(x)返回x所在的集合的代表 78
9在執行查詢操作時,要沿著父節點指標一直找下去,直到找到樹根為止。大家要注意途中的箭頭。 104
、實現並查集的標準**:
111 #include 12213
3const
int maxn = 100; /*
結點數目上線
*/14
4int pa[maxn]; /*
p[x]表示x的父節點
*/15
5int rank[maxn]; /*
rank[x]是x的高度的乙個上界
*/16617
7void make_set(int
x)18
8 22
1223
13int find_set(int
x)24
14 29
1930
20/*
按秩合併x,y所在的集合
*/31
21void union_set(int x, int
y)32
2243
33 }
並查集知識
其實並查集顧名思義就是有 合併集合 和 查詢集合 兩種操作的關於資料結構的一種演算法。並查集演算法不支援分割乙個集合。用集合中的某個元素來代表這個集合,該元素稱為集合的代表元。乙個集合內的所有元素組織成以代表元為根的樹形結構。對於每乙個元素 parent x 指向x在樹形結構上的父親節點。如果x是根...
並查集知識總結
1.非路徑壓縮 遞迴版 int64 findroot int64 x 非遞迴版 int64 findroot int64 x 查詢x的根節點 2.帶路徑壓縮 遞迴版 int64 findroot int64 x 找x的根節點 非遞迴版 int64 findroot int64 x return r ...
並查集 學習
yx th000 cherish yimi 昨天和今天學習了並查集和 trie 樹,並練習了三道入門題目,理解更為深刻,覺得有必要總結一下,這其中的內容定義之類的是取自網路,操作的說明解釋及程式的注釋部分為個人理解。並查集學習 l 並查集 union find sets 一種簡單的用途廣泛的集合.並...