並查集理解與應用
disjoint-set data structure
union-find data structure
merge-find set
記號
#define fori(n) for(int i=0;i<(n);i++)
#define Mem(x) memset((x),0,sizeof(x));
定義
樹形的數據結構,用於處理不相交集合的合並與查詢問題;
但不支持分割集合
應用
-
維護無向圖的連通性,
判斷兩個點是否在同一聯通塊內,增加一條邊是否產生環
實現
用數組儲存,[樹]結構實現
用數字代表元素:
parent(x)表示x在樹形結構上的父節點對應的數字
用代表元表示每個聯通塊.
舉例說明
現在有編號為 2,4,6的三個結點
如果 p[2]=4;表明 2號結點指向 4號結點
4號結點表示 {2,4} 聯通塊
操作
- CREATE_SET(x);用{x}創建新集合
- MERGE-SETS(x,y) 將x,y所屬集合合並
-
FIND-SET(x) 返回x所在集合的代表元
結構
使用兩個優化要點達到近乎常數時間
-
union by rank
-
作者更喜歡union by size …
讓更"矮"的樹的根指向更"高"的樹
建立數組rank[]來判斷樹深度,
CREATE-SET(x)時,rank[x]=0,
當MERGE時,rank更大的根將成為更小的根的parent;如果兩根rank相同,就任選一個父根,並且增加父根的root的值
-
作者更喜歡union by size …
-
union by rank
void unite(int x,int y){ int yy=find(y); //if(xx==yy) return; if(rank[xx]<rank[yy]) p[xx]=yy; else p[yy]=xx;
if(rank[xx]==rank[yy]) rank[xx]++; |
- 用path compression↓後,為了簡單起見,不再謝蓋rank的值,rank[]不再具有實際意義,仍能加快算法<可能需要深入了解相關論文<
- 也可以union by size(用樹的體積,這樣path compression 就無影響)
-
path compression 路徑壓縮
make each node on the find path point directly to the root
具體見find函數的代碼部分
加深印象
https://www.hackerearth.com/zh/practice/notes/disjoint-set-union-union-find/
可以訪問諸如↑網站,可以看到豐富的圖像,用圖片來解釋unionset
代碼
最基本的由三部分組成
void init(int n);
int find(int);
void unite(int a,int b);
前兩部分基本上都一樣
第三部分還可以優化,優化的方式也不同
init()
就那樣
void init(int n){fori(n)p[i]=i;}//註意題目中的編號是否從1開始 |
void init(int n){ //union by rank p[i]=i; } |
find()
int find(int x){return p[u]==u?u:p[u]=find(p[u]);} |
- unite()
void unite(int a,int b){ p[find(b)]=find(a); } //b→{a所在聯通塊} |
- unite() with union by rank
void unite(int x,int y){ y=find(y); //if(x==y) return; if(rank[x]<rank[y]) p[x]=y; else p[y]=x;
if(rank[x]==rank[y]) rank[x]++; |
+各種應用需要的代碼
/* 下標從 1 開始 */ int p[maxn]; set <int> point; //已經儲存了並查集中的所有點 int flag; //是否有環 int cnt; //不同集合的合並次數 int size[maxn]; // 表示聯通塊含有的元素數量 int init(int n) { point.clear(); fori(n) { p[i] = i; size[i] = 1; } cnt=0; flag=0; } int find(int u) { return p[u]==u?u:p[u]=find(p[u]); } void unite(int a,int b) { int aa=find(a); int bb=find(b); if(aa==bb){ flag=1; }else if(size(aa)<size(bb)){ cnt++; p[aa]=bb; size[bb]+=size[aa]; }else{ cnt++; p[bb]=aa; size[aa]+=size[bb]; } } |
時間復雜度研究
假設創建了n次集合,使用了f次find操作
不用任何優化
僅使用路徑壓縮
僅使用union by rank/size
兩次優化
並查集理解與應用