1. 程式人生 > >並查集的優化---路徑壓縮與啟發式合併

並查集的優化---路徑壓縮與啟發式合併

並查集的優化分為兩類:一種是 優化查詢的路徑壓縮,一種是啟發式合併(按集合大小合併與按秩(高)合併)

路徑壓縮

a.描述:如果查詢的總路徑過長,尤其是一條鏈的情況下,那麼樸素的查詢可能會超時。於是,每次在查詢完根節點後,可以將查詢路徑上的節點直接指向根節點。
b.缺點:在一定程度上破壞了樹的結構,也不便於記錄其他的附加資訊,如有特殊要求,請慎用(除非能夠轉化為與根節點的關係)
下面是遞迴版的程式碼

    int find(int x) //查詢x的祖先節點
    {  // fa[x]表示x的父親節點
        return fa[x]=(fa[x]!=x?find(fa[x]):x);
    }  //在返回的過程,當前x的節點也順便指向了根節點

啟發式合併

  • 按秩(高)合併
    描述:使用秩來表示樹高度的上界,在合併時,總是將具有較小秩的樹根指向具有較大秩的樹根,這樣找祖先會減少遞迴迭代的次數,最壞只有logN次。
    注意:由於需要記錄rank[]來表示高度,所以一般不與路徑壓縮同用(會破壞數的高度)
    void join(int x,int y)
    {  
        int xx=find(x),yy=find(y); //xx,yy 分別表示x與y的祖先節點
        if(xx==yy) return ;
        if(rank[xx]>rank[yy]) //rank[] 表示樹的高度
fa[yy]=xx; //將yy接到xx上去 //由於xx的高度比yy的大,所以無需更新高度 rank[xx]>=rank[yy]+1 else { fa[xx]=yy;//將xx接到yy上去 if(rank[xx]==rank[yy]) rank[yy]++; } }
  • 按集合大小合併(啟發式合併)
    描述:與按秩合併類似,只是將合併的參照改為集合的大小
    優點:可與路徑壓縮同用
    均攤複雜度: log2N
void join(int
x,int y) { int xx=find(x),yy=find(y); if(xx==yy) return ; if(size[xx]>size[yy]) //記錄size[]集合大小 { size[xx]+=size[yy]; fa[yy]=xx; } else { size[yy]+=size[xx]; fa[xx]=yy; } }

PS:只有同時採用”路徑壓縮”與”啟發式合併”時,每次find的時間複雜度才能降為α(n)

以上就是並查集中查與並的優化,至於優化的效率如何,可自行找試題測試

洛谷模板題

帶權並查集