算法筆記(1)- 並查集

算法筆記(1)

1. 並查集

並查集,在一些有N個元素的集合應用問題中,我們通常是在開始時讓每個元素構成一個單元素的集合,然後按一定順序將屬於同一組的元素所在的集合合併,其間要反覆查找一個元素在哪個集合中。並查集是一種樹型的數據結構,用於處理一些不相交集合(Disjoint Sets)的合併及查詢問題。常常在使用中以森林來表示。

主要有兩種操作:

  • **合併(union):**把兩個不相交的集合合併爲一個集合。
  • **查詢(find):**查詢兩個元素是否在同一個集合中。

並查集的引入:

並查集的重要思想在於,用集合中的一個元素代表集合。有一個有趣的比喻,把集合比喻成幫派,而代表元素則是幫主。接下來用這個比喻,看看並查集是如何運作的。

在這裏插入圖片描述

最開始,所有大俠各自爲戰。他們各自的幫助自然就是自己。(對於只有一個元素的集合,代表元素自然是唯一的那個元素)。

現在1號和3號比武,假設1號贏了(這裏具體是誰贏的不重要),那麼3號就認1號做幫主(合併1號和3號所在的集合,1號爲代表元素)

在這裏插入圖片描述

現在2號想和3號比武(合併3號和2號所在的集合),但3號表示,別跟我打,讓我幫主來收拾你(合併代表元素)。不妨設這次又是1號贏了,那麼2號也認1號做幫主。

在這裏插入圖片描述
現在我們假設4,5,6號也進行了一番幫派合併,江湖局勢變成下面這樣:

在這裏插入圖片描述

現在假設2號想和6號比,和剛纔說的一樣,喊幫主1號和4號出來打一架。1號戰勝後,4號認1號爲幫主,當然他的手下也都是跟着投降了。

在這裏插入圖片描述
好了,比喻結束了。如果你有一點圖論基礎,相信你已經覺察到,這是一個狀的結構,要尋找集合的代表元素,只需要一層一層往上訪問父節點(圖中箭頭所指的圓),直達樹的根節點(圖中橙色的圓)即可。根節點的父節點是它自己。我們可以直接把它畫成一棵樹:

在這裏插入圖片描述
用這種方法,我們可以寫出最簡單的並查集代碼。

初始化

public void init(int n){
    for(int i = 0;i<=n;i++){
        fa[i] = i;
    }
}

假如有編號1,2,3,…,n的n個元素,我們用一個數組fa[]來存儲每個元素的父節點(因爲每個元素有且只有一個父節點,所以這樣是可行的)。一開始,我們先將它們的父節點設爲自己。

查詢

public int find(int index) {
    while(fa[index] != index){
        index = fa[index];
    }
    return index;
}

要判斷兩個元素是否屬於一個集合,只需要看它們的根節點是否相同即可。

合併

public void union(int i,int j){
    fa[find(i)]=find[j];
}

合併操作也很簡單,先找到兩個集合的代表元素,然後將前者的父節點設爲後者即可。當然也可以將後者的父節點設爲前者。

2. 路徑壓縮

最簡單的並查集效率是比較低的。例如,看下面的這個場景:

在這裏插入圖片描述
現在我們想要union(2,3),於是從2找到1,fa[1]=3,於是變成了這樣:

在這裏插入圖片描述
然後我們又找來了一個元素4,並需要執行union(2,4):

在這裏插入圖片描述
從2找到1,再找到3,然後fa[3]=4,於是變成了這樣:

在這裏插入圖片描述
這樣可能會形成一條長長的鏈,隨着鏈越來越長,我們想要從底部找到根節點會變得越來越難。

我們就可以使用路徑壓縮的方法。既然我們只關心一個元素對應的根節點,那我們希望每個元素到根節點的路徑儘可能的短,最好只需要一步,像這樣:

在這裏插入圖片描述

只要我們在查詢的過程中,把沿途的每個節點的父節點都設爲根節點即可。

合併(路徑壓縮)

public int find(int x){
    if(x==fa[x])
    	return x;
    else{
        fa[x]=find(fa[x]);//父節點設爲根節點
        return fa[x];//返回父節點
    }   
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章