算法:並查集


算法:並查集

快速掌握

理解算法

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

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

  由於支持這兩種操作,一個不相交集也常被稱為聯合-查找數據結構(union-find data structure)或合並-查找集合(merge-find set)。其他的重要方法,MakeSet,用於建立單元素集合。有了這些方法,許多經典的划分問題可以被解決。

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

  

 

說明:左邊是A,筆誤!

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

並查集(樹)

  並查集(樹)是一種將一個集合以樹形結構進行組合的數據結構,如上圖所示。其中每一個節點保存着到它的父節點的引用

  在並查集樹中,每個集合的代表即是集合的根節點

  • “查找”根據其父節點的引用向根行進直到到底樹根
  • “聯合”將兩棵樹合並到一起,這通過將一棵樹的根連接到另一棵樹的根

  實現這樣操作的偽代碼如下:

 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)

優化后的MakeSetUnion偽代碼:

 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遞歸地經過樹,改變每一個節點的引用到根節點。得到的樹將更加扁平,為以后直接或者間接引用節點的操作加速。

  

這兒是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不相同*/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM