資料結構(十一)並查集的實現和優化
阿新 • • 發佈:2018-12-20
並查集
1. 陣列儲存的基礎實現
此時並查集用一個結構體儲存,data 存值(下標+1),parent 存父結點下標 查詢操作中先線性查詢到需要找到的值,再迴圈查詢其根結點 並操作先找到兩合併陣列的樹根,如果不相等,把一棵樹掛到另一棵樹根下
#include<iostream>
#include<cstring>
#define MaxSize 1000
typedef int ElementType;
typedef struct{
ElementType Data; // 存值
int parent; // 指向父結點
}SetType;
using namespace std;
// 查詢
int Find(SetType s[],ElementType x){
int i;
// 找到陣列中該值對應的下標
for(i=0;i<MaxSize && s[i].Data!=x;i++);
if(MaxSize <= i) // 如果沒有找到,返回 -1
return -1;
// 找到該結點的根結點
for(;s[i].parent >= 0;i = s[i].parent);
return i; // 返回根結點在陣列 s 中的下標
}
// 並
void Union(SetType s[],ElementType x1, ElementType x2){
int root1 = Find(s,x1); // 找到 x1 的根結點下標
int root2 = Find(s,x2); // 找到 x2 的根結點下標
// 如果根結點的下標不同,說明不是一個集合
if(root1 != root2){
s[root1].parent = root2; // 把 x1 掛到 x2 的集合
}
}
int main(){
SetType s[MaxSize];
// 初始化陣列,父結點全部指向 -1
for(int i=0;i<MaxSize;i++){
s[i].Data = i+1;
s[ i].parent = -1;
}
cout<<Find(s,5)<<endl;
Union(s,3,5);
cout<<Find(s,4)<<endl;
cout<<Find(s,3)<<endl;
Union(s,1,3);
Union(s,2,4);
Union(s,8,6);
cout<<Find(s,6)<<endl;
cout<<Find(s,8)<<endl;
return 0;
}
2. 優化儲存結構
觀察上面的結構體,其實值和陣列下標差一,那可以直接把值 1 ~ n 對映為下標的 0 ~ n-1 查詢操作直接去找其父結點 並操作直接把一棵樹掛另一棵樹上去
#include<cstdio>
#include<iostream>
#define MaxSize 10005
typedef int SetType;
using namespace std;
// 初始化
void Init(SetType s[]){
for(int i=0;i<MaxSize;i++)
s[i] = -1;
}
// 查詢
int Find(SetType s[],int x){
// 直接去找父結點
for(;s[x]>=0;x = s[x]);
return x;
}
// 並
void Union(SetType s[],int x1,int x2){
// 直接合並
s[x1] = x2;
}
int main(){
// 此時陣列下標既做下標又存值,陣列值存父結點下標
SetType s[MaxSize];
Init(s);
cout<<Find(s,5)<<endl;
Union(s,3,5);
cout<<Find(s,4)<<endl;
cout<<Find(s,3)<<endl;
Union(s,1,3);
Union(s,2,4);
Union(s,8,6);
cout<<Find(s,6)<<endl;
cout<<Find(s,8)<<endl;
return 0;
}
3. 按秩歸併優化 Union
當多次並操作時,其中一個運算元固定,另一個運算元一直遞增或一直遞減時,樹會退化成單鏈表
1. 根據樹高歸併
一種解決辦法是每次將 “矮” 的樹掛到 "高"樹上去 樹高資訊儲存在根結點的陣列值中(之前根結點的陣列值都存的 -1,現在存 -樹高) 這樣當"矮"樹掛到"高"樹上,樹的高度不會增加,只有當兩棵樹一樣高,高度才+1
// 並
void Union(SetType s[],int x1,int x2){
// x1 更高,負數啊!
if(s[x1] < s[x2]){
s[x2] = x1;
}else{
// 樹高相等
if(s[x1] == s[x2])
s[x2]--; // 樹高 +1 ,是負數
s[x1] = x2;
}
}
2. 根據規模歸併
另一種解決辦法是規模小的樹掛到規模更高的樹上去 規模資訊儲存在根結點的陣列值中(之前根結點的陣列值都存的 -1,現在存 -元素個數) 這樣當小樹掛到大樹上,只有較少的樹會高一層
// 並
void Union(SetType s[],int x1,int x2){
// x1 規模更大,負數啊!
if(s[x1] < s[x2]){
s[x1] += s[x2]; // 兩樹合併,規模相加
s[x2] = x1; // x2 掛到 x1 上
}else{
s[x2] += s[x1]; // 兩樹合併,規模相加
s[x1] = x2;
}
}
4. 路徑壓縮優化 Find
查詢不可避免的越查越深,路徑壓縮可以把待查詢結點與根結點之間的一系列結點的上一結點都變為根結點,即當查詢 D 後:
// 查詢
int Find(SetType s[],int x){
if(s[x] < 0) // 本身已經是根
return x;
else // 1. 找到根 2. 把根變成 x 的父結點 3.再返回根
return s[x] = Find(s,s[x]);
}