1. 程式人生 > >並查集理解與應用

並查集理解與應用

one 優化 rank 交集 了解 分享 ont group table

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的值

void unite(int x,int y){
  int xx=find(x);

  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
  fori(n){
    rank[i]=0;

    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){
  x=find(x);

  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次集合,使用了ffind操作

不用任何優化

技術分享圖片

僅使用路徑壓縮

技術分享圖片

僅使用union by rank/size

技術分享圖片

兩次優化

技術分享圖片

技術分享圖片

並查集理解與應用