什麼是並查集
並查集是一種樹形的數據結構,,用於處理一些不想交集合的合併即查詢問題,我們可以通過並查集以接近O(1)
的時間完成兩個不相交集合的合併,並且以O(1)
的時間判斷一個元素屬於哪個集合
理解並查集
假設我們有[1, 2, 3, 4, 5]
和[6, 7, 8, 9]
兩個集合,以並查集的思想,我們要以如下方式存儲它
1 6
| |
2 7
| |
3 8
| |
4 9
|
5
這實際上是兩個每個節點只包含一個子節點的樹狀結構,這樣樹的根節點即表示集合的名稱,而樹種所有的葉子節點都存在唯一的一個根節點,我們可以根據這個性質來判斷某個元素是否屬於一個集合
那麼我們如何實現兩個集合的合併呢,如果是普通的使用數組存儲的集合的話,我們一定需要將其中一個數組整個複製進另一個數組中,這需要很大的時間,並且我們還需要考慮數組的容量問題,如果使用並查集,我們只需要將一個集合的根節點接在另一個集合中,集合完成兩個集合的合併,如下圖
1————6
| |
2 7
| |
3 8
| |
4 9
|
5
這就是使用並查集存儲不想交集合以及實現集合合併和查詢元素是否屬於某集合的方法
並查集的優化
通過上述的過程,我們發現合併兩個集合的操作十分簡單,但是判斷一個元素是否屬於某個集合的過程顯得十分複雜,我們需要從一個葉子節點一直回溯到根節點來判斷它是否屬於一個集合,針對這個問題有一種非常巧妙的優化方式——路徑壓縮
我們發現,並查集實際上是一顆並不規則的樹,它的結構不是固定的,每個節點原則上可以擁有無數個節點,所以如果我們將一個集合中的所有元素都直接和根節點連接,那麼通過葉子節點找到根節點的回溯過程就會減少很多時間,如下圖:
2
|
5——1——3
|
4
這樣我們找到一個節點的時間就近乎可以壓縮到O(1)
了
代碼
在代碼中,我們使用find
函數來實現找到元素所在的集合及路徑壓縮,我們使用p[x]
來表示x
屬於哪個集合
#include <iostream>
using namespace std;
const int N = 100010;
int p[N];
int n, m;
// 返回集合名稱並實現路徑壓縮
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) p[i] = i;
while (m--) {
char op[2];
int a, b;
scanf("%s%d%d", op, &a, &b);
// 合併操作
if(op[0] == 'M') p[find(a)] = find(b);
// 查詢操作
else {
if (find(a) == find(b)) puts("Yes");
else puts("No");
}
}
return 0;
}