並查集 啓發式合併
並查集,這是個比較簡單的東西,用森林模擬集合,集合合併則是兩顆樹的合併,查找集合則是尋找某棵樹的根節點
因爲不可避免的會遇到各種合併操作,,所以可能導致某棵樹的深度較大,,對應的則出現了並查集的路徑壓縮還有啓發式合併
這裏不將路徑壓縮,主要是這個東西比較普及了,而且理解起來不難。
所以直接進入正題:啓發式合併
啓發式合併
第一次看到這個名稱,實在劉汝佳的書上,當時還驚訝,並查集寫了一年了,還真不知道這個啓發式合併,接着看,發現實現是使用了一個rank數組,,,原書這麼說的:
一個優化是:把小的樹合併到大樹中,這樣會讓深度不太大。這個優化稱爲啓發式合併。rank[i] 表示 i 的秩,並用秩來代替深度做剛纔提到的啓發式合併。
然後就懵了,秩這個東西怎麼理解……
於是上網尋找,發現很多blog所描述的都和書上幾乎一模一樣。。
所以自己想辦法理解,,還好理解了
數組rank的意義
這裏舉個例子
假設我們把這些樹看成——一個個城市,而這些城市,因發展的程度被分爲不同等級,比如小型城市,中型城市,大型城市,城市與城市之間可以合併,並且規定,小型城市和小型城市合併,其中一個城市會升級爲中型城市,兩個中型城市合併,其中一個城市會升級爲高級城市
我們再次做一個更嚴密的規則:
- 只有兩個同級城市合併,其中一個城市才能升級
- 小型城市合併到大型城市中,大型城市不升級
- 等級高的城市必須由等級低的城市組成
回到樹——這個問題
我們假設rank[a]表示樹a的等級,姑且這麼稱呼吧,叫做名稱也可以。根據上面的規則(轉換一下),如果一棵樹的等級較高,則這棵樹的”規模“會比較高,也就是深度會深一些,根據之前的合併優化規則:
一個優化是:把小的樹合併到大樹中,這樣會讓深度不太大。
也就是把等級較低的樹合併到等級較高的樹上
總結
我們寫一個合併函數,這個函數需要根據兩棵樹的等級(rank)來判斷哪棵樹合併到另一棵樹,並且判斷一棵樹是否需要升級
代碼:
void union_ (int a,int b) {
int x = find_(a), y = find_(b);
if (ranks[x] > ranks[y]) {//兩棵樹一定不同級,不存在升級操作
bcset[y] = x;
} else {
bcset[x] = y;
if (ranks[x] == ranks[y]) {//兩棵同等級的樹合併,其中一棵樹升級
ranks[y] ++;
}
}
}