一. 並查集能用來幹什麼
求一個圖的連通分量的個數。
二. 並查集基本操作
並查集用來查詢一個元素是否在一個集合中 + 合併兩個不相交的集合。
他就這麼兩個元操作;基於這兩個操作的衍生操作有
- 比較兩個元素是否在一個並查集中——比較兩個並查集中的代表元素是否相同
- 往一個並查集中添加元素——將該元素單獨作爲一個並查集,然後和目標集合做並集操作。
三. 並查集優化——路徑壓縮算法
我們在學習BST的時候知道,一棵二叉樹查找的時間複雜度和它的高度相關。如果一棵二叉樹呈線性,那麼搜索元素的時間複雜度就是O(n)。在並查集中也是如此,我們得想方設法地降低樹的高度。
有一點要注意的是其實並查集對應的無向圖其實是一棵多叉樹,這一點反而有利於我們的編程。
路徑優化算法就是用來降低我們樹的高度的,它主要從我們並查集的兩個操作出發:
· 查詢元素
我們在查詢元素的時候順便重構了一下查詢的分支,把該路徑上所有的節點都直接與根節點相連。見下圖所示:
· 合併集合
我們不能像TBT一樣把樹掛載到根節點的左子樹中最右的節點下(其實這就是完全相反的做法)
我們應該利用其多叉樹的性質把一個並查集直接掛載到另一個並查集的根節點上。
我們最好把高度小的樹掛載到高度大的樹上,這樣我們的高度就不會有變化。
當然如果兩棵樹的高度相等,那就要使高度+1了。
(見此圖的下面兩張圖)
四. 模板
其實這個模板寫的不太好(指變量命名方面
但是我覺得只要寫過幾遍,看了模板以後能夠明白其中的思想,那就夠了。
// 像union set這種數據結構真的不適合使用成員方法來實現
// 還是像鏈表一樣老老實實地用非成員方法來做好了
// 發現還是使用數組比較方便
constexpr int kNum = 100000;
int parent_[kNum]; // 記錄每一個節點的父親節點編號
int rank_[kNum]; // 用bits/stdc++.h還不能使用rank,不然會重名發生ambigous錯誤
void Initialize() {
// 初始化
for (int i = 0; i < kNum; ++i) {
parent_[i] = i;
rank_[i] = i;
}
}
int Find(int x) {
// 找到相應並查集的根節點
// 根節點的特徵
if (x == parent_[x]) {
return x;
}
return parent_[x] = Find(parent_[x]); // 繼續查找+路徑壓縮
}
void Union(int x, int y) {
int parent_x = Find(x);
int parent_y = Find(y);
// 如果在同一個並查集內
if (parent_x == parent_y) {
return;
}
// 如果不在一個並查集內, 根據rank來合併並查集
// 注意對rank的更新
if (rank_[parent_x] > rank_[parent_y]) {
parent_[parent_y] = parent_x; // y掛到x上
} else {
if (rank_[parent_x] == rank_[parent_y]) {
++rank_[parent_y]; // 由於接下來要掛到y上
}
parent_[parent_x] = parent_y; // x掛到y上
}