hdu1232 並查集總結
阿新 • • 發佈:2021-07-01
前言
在一些有N個元素的集合應用問題中,我們通常是在開始時讓每個元素構成一個單元素的集合,然後按一定順序將屬於同一組的元素所在的集合合併,其間要反覆查詢一個元素在哪個集合中。
這一類問題其特點是看似並不複雜,但資料量極大,若用正常的資料結構來描述的話,往往在空間上過大,計算機無法承受;即使在空間上勉強通過,執行的時間複雜度也極高,根本就不可能在規定的執行時間(1~3秒)內計算出試題需要的結果,只能用並查集來描述。
定義
並查集(Disjoint Set),即“不相交集合”,是一種樹型的資料結構,用於處理一些不相交集合(Disjoint Sets)的合併及查詢問題。常常在使用中以森林來表示。集就是讓每個元素構成一個單元素的集合,也就是按一定順序將屬於同一組的元素所在的集合合併。
將編號分別為1…N的N個物件劃分為不相交集合,在每個集合中,選擇其中某個元素代表所在集合。
常見兩種操作:
- 合併兩個集合
- 查詢某元素屬於哪個集合
用編號最小的元素標記所在集合;定義一個數組set[1...n]
,其中set[i]
表示元素i 所在的集合;
演算法實現
查詢
時間複雜度:\(O(1)\)
find1(x)
{
return set[x];
}
合併
時間複雜度:\(O(n)\)
Merge1(a,b) { i = min(a,b); j = max(a,b); for (k = 1; k <= N; k++) { if (set[k] == j) set[k] = i; } }
對於合併操作,必須搜尋全部元素!有沒有可以改進的地方呢?
演算法的優化
使用樹結構
每個集合用一棵“有根樹”表示,定義陣列set[1...n]
set[i] = i
,則 i 表示本集合,並且是集合所對應樹的根set[i] = j
,j<>i,則 j 是 i 的父節點
查詢
時間複雜度(最壞):\(O(n)\)
find2(x)
{
r = x;
while (set[r] != r)
r = set[r];
return r;
}
合併
時間複雜度:\(O(1)\)
merge2(a, b) { if (a<b) set[b] = a; else set[a] = b; }
避免最壞情況
方法:將深度小的樹合併到深度大的樹
實現:假設兩棵樹的深度分別為h1和h2, 合併後的樹的高度為h,則
效果:任意順序的合併操作以後,包含k個節點的樹的最大高度不超過\(\log_2{k}\)
查詢
時間複雜度:\(O(\log_2{n})\)
find2(x)
{
r = x;
while (set[r] != r)
r = set[r];
return r;
}
合併
時間複雜度:\(O(1)\)
merge3(a,b)
{
if (height(a) == height(b)) {
height(a) = height(a) + 1;
set[b] = a;
} else if (height(a) < height(b)) {
set[a] = b;
} else {
set[b] = a;
}
}
路徑壓縮
思想:每次查詢的時候,如果路徑較長,則修改資訊,以便下次查詢的時候速度更快。
步驟:
- 找到根結點
- 修改查詢路徑上的所有節點,將它們都指向根結點
路徑壓縮示意圖:
查詢
find3(x)
{
r = x;
while (set[r] != r) //迴圈結束,則找到根節點
r = set[r];
i = x;
while (i != r) //本迴圈修改查詢路徑中所有節點
{
j = set[i];
set[i] = r;
i = j;
}
}
hdu1232
#include<stdio.h>
int x[1005];
int min(int a,int b);
int max(int a,int b);
void xs(int a,int b);
int fine(int a);
int main()
{
int n,m,i,a,b;
while(scanf("%d",&n)&&n)
{
int sum = -1;
scanf("%d",&m);
for(i=1;i<=n;i++) x[i]=i; //首先把各自的父節點設為自身
for(i=1;i<=m;i++)
{
scanf("%d%d",&a,&b);
xs(a,b); //合併兩個集合
}
for(i=1;i<=n;i++)
{
if(x[i]==i) sum++; //算出(最後不同集合的個數-1)即為所求
}
printf("%d\n",sum);
}
return 0;
}
int min(int a,int b)
{
return a<b ? a : b;
}
int max(int a,int b)
{
return a>b ? a : b;
}
int fine(int a)
{
if(x[a]==a) return a;
else return fine(x[a]);
}
void xs(int a,int b)
{
x[max(fine(a),fine(b))] = min(fine(a),fine(b));
}