算法:並查集

算法:並查集

快速掌握

理解算法

  在計算機科學中,並查集是一種樹型的數據結構,用於處理一些不交集(Disjoint Sets)的合併及查詢問題。有一個聯合-查找算法union-find algorithm)定義了兩個用於此數據結構的操做:php

  • Find肯定元素屬於哪個子集。這個肯定方法就是不斷向上查找找到它的根節點,它能夠被用來肯定兩個元素是否屬於同一子集
  • Union將兩個子集合併成同一個集合

  因爲支持這兩種操做,一個不相交集也常被稱爲聯合-查找數據結構(union-find data structure)或合併-查找集合(merge-find set)。其餘的重要方法,MakeSet,用於創建單元素集合。有了這些方法,許多經典的劃分問題能夠被解決。java

  爲了更加精確的定義這些方法,須要定義如何表示集合。一種經常使用的策略是爲每一個集合選定一個固定的元素,稱爲表明,以表示整個集合。接着,Find(x) 返回 x 所屬集合的表明,而 Union 使用兩個集合的表明做爲參數算法

  

  上圖中簡單示了並查集的兩個操做,一個是FIND,一個UNION數組

並查集森林

  並查集森林是一種將每個集合以表示的數據結構,如上圖所示。其中每個節點保存着到它的父節點的引用(見意大利麪條堆棧)。這個數據結構最先由Bernard A. GallerMichael J. Fischer於1964年提出,[1]可是通過了數年才完成了精確的分析。數據結構

  在並查集森林中,每一個集合的表明便是集合的根節點「查找」根據其父節點的引用向根行進直到到底樹根「聯合」將兩棵樹合併到一塊兒,這經過將一棵樹的根鏈接到另外一棵樹的根。實現這樣操做的一種方法是函數

 function MakeSet(x)
     x.parent := x

 function Find(x)
     if x.parent == x
        return x
     else
        return Find(x.parent)

 function Union(x, y)
     xRoot := Find(x)
     yRoot := Find(y)
     xRoot.parent := yRoot

  這是並查集森林的最基礎的表示方法,這個方法不會比鏈表法好,這是由於建立的樹可能會嚴重不平衡;然而,能夠用兩種辦法優化。優化

優化方法一:按秩合併

  第一種方法,稱爲「按秩合併」,即老是將更小的樹鏈接至更大的樹上。由於影響運行時間的是樹的深度,更小的樹添加到更深的樹的根上將不會增長秩除非它們的秩相同。在這個算法中,術語「秩」替代了「深度」,由於同時應用了路徑壓縮時(見下文)秩將不會與高度相同。單元素的樹的秩定義爲0,當兩棵秩同爲r的樹聯合時,它們的秩r+1。只使用這個方法將使最壞的運行時間提升至每一個MakeSet、Union或Find操做O(\log n)spa

優化後的MakeSetUnion僞代碼:3d

 function MakeSet(x)
     x.parent := x
     x.rank   := 0

 function Union(x, y)
     xRoot := Find(x)
     yRoot := Find(y)
     if xRoot == yRoot
         return

     // x和y不在同一個集合,合併它們。
     if xRoot.rank < yRoot.rank
         xRoot.parent := yRoot
     else if xRoot.rank > yRoot.rank
         yRoot.parent := xRoot
     else
         yRoot.parent := xRoot
         xRoot.rank := xRoot.rank + 1  

優化方法二:路徑壓縮 

  第二個優化,稱爲「路徑壓縮」,是一種在執行「查找」時扁平化樹結構的方法。關鍵在於在路徑上的每一個節點均可以直接鏈接到根上;他們都有一樣的表示方法。爲了達到這樣的效果,Find遞歸地通過樹,改變每個節點的引用到根節點。獲得的樹將更加扁平,爲之後直接或者間接引用節點的操做加速。code

  

這兒是Find

 function Find(x)
     if x.parent != x
        x.parent := Find(x.parent)
     return x.parent  

  這兩種方法的優點互補,同時使用兩者的程序每一個操做的平均時間僅爲O(\alpha (n))\alpha (n)n=f(x)=A(x,x)的反函數,其中A是急速增長的阿克曼函數。由於\alpha (n)是其的反函數,故\alpha (n)n十分巨大時仍是小於5。所以,平均運行時間是一個極小的常數。

  實際上,這是漸近最優算法:Fredman和Saks在1989年解釋了\Omega (\alpha (n))的平均時間內能夠得到任何並查集。

並查集算法-Java實現

package search;

public class UnionFindSet {
    private int[] parents_;
    private int[] ranks_;

    public UnionFindSet(int n)
    {
        parents_ = new int[n+1];
        ranks_ = new int[n+1];
        for(int i=0;i<=n;i++)
        {
            parents_[i]=i;
            ranks_[i]=i;
        }
    }

    public boolean Union(int u,int v)
    {
        int pu = Find(u);
        int pv = Find(v);
        if(pu==pv)
            return false;
        if (ranks_[pv] > ranks_[pu])
            parents_[pu] = pv;
        else if (ranks_[pu] > ranks_[pv])
            parents_[pv] = pu;
        else {
            parents_[pv] = pu;
            ranks_[pu] += 1;
        }
        return true;
    }

    public int Find(int u)
    {
        while (parents_[u]!=u)
        {
            parents_[u]=parents_[parents_[u]];
            u=parents_[u];
        }
        return u;
    }

}

主要操做

合併兩個不相交集合

  操做很簡單:先設置一個數組(陣列)Father[x],表示x的「父親」的編號。 那麼,合併兩個不相交集合的方法就是,找到其中一個集合最父親的父親(也就是最久遠的祖先),將另一個集合的最久遠的祖先的父親指向它

void Union(int x,int y)
{
    fx = getfather(x);
    fy = getfather(y);
    if(fy!=fx)
       father[fx]=fy;
}

判斷兩個元素是否屬於同一集合

  仍然使用上面的數組。則本操做便可轉換爲尋找兩個元素的最久遠祖先是否相同尋找祖先能夠採用遞歸實現,見後面的路徑壓縮算法。

bool same(int x,int y)
{
   return getfather(x)==getfather(y);
}
/*返回true 表示相同根結點,返回false不相同*/
相關文章
相關標籤/搜索