高階資料結構:並查集
阿新 • • 發佈:2021-07-12
並查集(Union-find Sets)是一種樹型的資料結構,用於處理一些不相交集合的合併及查詢問題。
並查集
並查集(Union-find Sets)是一種樹型的資料結構,用於處理一些不相交集合的合併及查詢問題。
並查集的思想是用一個數組表示了整片森林(parent),樹的根節點唯一標識了一個集合,我們只要找到了某個元素的的樹根,就能確定它在哪個集合裡。
yxc模板
並查集 —— 模板題:AcWing 836. 合併集合, AcWing 837. 連通塊中點的數量
(1)樸素並查集:
int p[N]; //儲存每個點的祖宗節點 // 返回x的祖宗節點 int find(int x)// 路徑壓縮 { if (p[x] != x) p[x] = find(p[x]); return p[x]; } // 初始化,假定節點編號是1~n for (int i = 1; i <= n; i ++ ) p[i] = i; // 合併a和b所在的兩個集合: p[find(a)] = find(b); // 不要寫成p[a] = b;
(2)維護size的並查集:
int p[N], size[N]; //p[]儲存每個點的祖宗節點, size[]只有祖宗節點的有意義,表示祖宗節點所在集合中的點的數量 // 返回x的祖宗節點 int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } // 初始化,假定節點編號是1~n for (int i = 1; i <= n; i ++ ) { p[i] = i; size[i] = 1; } // 合併a和b所在的兩個集合: size[find(b)] += size[find(a)]; p[find(a)] = find(b);
(3)維護到祖宗節點距離的並查集:
int p[N], d[N]; //p[]儲存每個點的祖宗節點, d[x]儲存x到p[x]的距離 // 返回x的祖宗節點 int find(int x) { if (p[x] != x) { int u = find(p[x]); d[x] += d[p[x]]; p[x] = u; } return p[x]; } // 初始化,假定節點編號是1~n for (int i = 1; i <= n; i ++ ) { p[i] = i; d[i] = 0; } // 合併a和b所在的兩個集合: p[find(a)] = find(b); d[find(a)] = distance; // 根據具體問題,初始化find(a)的偏移量
原理介紹
參考:演算法訓練營進階篇。
假設存在一個龐大的部落,人數眾多,我們需要判斷給出的兩人是否具有親戚關係。
現給定親戚關係的兩條重要性質:
- 傳遞性:若x、y是親戚,y、z是親戚,那麼x、z是親戚;
- 集合合併(連通集合):若x、y是親戚,則x的親戚也是y的親戚,y的親戚也是x的親戚。
我們可以用並查集來處理具有這兩種性質的親戚關係的判定問題。
演算法步驟:1.初始化;2.查詢;3.合併。
y總的模板真的很不錯,hh。
還是直接上例題。
初始化時並查集編號從1開始!
洛谷P3367並查集(模板題)
題目描述
如題,現在有一個並查集,你需要完成合並和查詢操作。
輸入格式
第一行包含兩個整數 N,M,表示共有 N 個元素和 M 個操作。
接下來 M 行,每行包含三個整數 Z,X,Y
當 Z=1 時,將 X與Y所在的集合合併。
當 Z=2 時,輸出 X與Y是否在同一集合內,是的輸出 Y ;否則輸出 N 。
輸出格式
對於每一個 Z=2 的操作,都有一行輸出,每行包含一個大寫字母,為 Y 或者 N 。
輸入輸出樣例
輸入
4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4
輸出
N
Y
N
Y
對於 100\%100% 的資料,1≤N≤10^4 ,1≤M≤2×10^5
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 100010;
int p[N];// 儲存每個點的祖宗結點
int n,m;
int find(int a){// 遞迴找到祖宗
if (p[a] != a) p[a] = find(p[a]);
return p[a];
}
int main(){
scanf("%d%d",&n,&m);
// 初始化編號
for (int i = 1;i <= n;i++) p[i] = i;
int z,x,y;char res;
while (m--){
scanf("%d%d%d",&z,&x,&y);
if (z == 1) p[find(x)] = find(y);// 合併集合操作
else{
res = find(x) == find(y) ? 'Y' : 'N';
printf("%c\n",res);
}
}
return 0;
}
初始化:剛開始的時候每個點就是一個獨立的集合,且這個集合樹的樹根就是本身。
查詢:路徑壓縮,每次執行find操作的時候,把訪問過的節點(也就是所有元素的父親),都統統指向樹根祖宗.這種方法可以避免出題人刻意卡掉鏈式結構。
N次合併M次查詢的時間複雜度為:O(M*Alpha(N)),Alpha是Ackerman函式的某個反函式,在很大範圍內它的值不超過4,接近線性,所以可以近似看成是:O(1)。