並查集-05-樹8 File Transfer
阿新 • • 發佈:2019-01-30
- 分析
這道題考察的是並查集的基本操作,核心操作就兩個:
1.查詢元素所在的集合
2.並集,即合併兩個集合
首先可以用一個數組來儲存這N個計算機和它們的集合。
陣列下標i表示計算機i,S[i] 來體現所在的集合。當 S[i] 為負數時,表示它就是集合的根,S[i] 為正數時,表示父結點是S[i]。 - 基本的程式碼
#include<stdio.h>
#include<string.h>
#define maxn 10001
int S[maxn]; //S[i]中,負數表示是根結點,正數表示父結點
int connected;
int myFind(int x)
{
int parent = x;
while((parent=S[x]) > 0)
{
x = parent;
}
return x;
}
void myUnion(int x1, int x2)
{
int root1 = myFind(x1);
int root2 = myFind(x2);
if(root1 != root2)
{
S[root2] = root1;
connected--;
}
}
int main()
{
//freopen("set.txt","r",stdin);
int N;
char op;
int num1,num2;
int root1,root2;
scanf("%d",&N);
memset(S,-1,sizeof(int)*N);
connected = N;
while(1)
{
scanf("%c", &op);
if(op == 'S') break;
else if(op == 'I'){
scanf("%d%d",&num1,&num2);
myUnion(num1,num2);
}else if(op == 'C'){
scanf("%d%d",&num1,&num2);
root1 = myFind(num1);
root2 = myFind(num2);
if(root1 == root2) printf("yes\n");
else printf("no\n");
}
}
if(connected == 1){
printf("The network is connected.\n");
}else{
printf("There are %d components.\n",connected);
}
return 0;
}
程式基本就是這樣子的,提交之後是部分正確,因為沒有對歸併(並集)操作進行優化,會超時。
我們具體來看一下並集操作
void myUnion(int x1, int x2)
{
int root1 = myFind(x1);
int root2 = myFind(x2);
if(root1 != root2)
{
S[root2] = root1;
connected--;
}
}
我們每次歸併都是將 第二個集合 貼到 第一個集合上面,如果每次都是正好將 高度很高的集合 貼到了 高度比較低的集合上,那麼集合樹的高度一直在增加,甚至可能高度就是N,所以每次myFind操作都需要遍歷很高的樹,對N次歸併,時間複雜度是O(N^2)。N是10的4次方數量級,平方之後顯然會超時。
如何進行優化呢?
我們首先可以想到可以將矮樹貼到高樹上面去。或者將規模小的樹貼到規模大的樹。
那麼如何儲存樹的高度或規模呢?我們之前,S[root]中儲存的都是-1,負數就表示是根結點,那麼我們可以用這個負數來儲存樹高或規模。
根據樹高進行歸併虛擬碼:
//S[i] 都初始化為 -1
if(root1高度 > root2高度)
S[root2] = root1;
else{
if(root2高度 == root1高度) 樹高++
S[root1] = root2;
}
根據樹高進行歸併具體程式碼:
if(S[root1] < S[root2]){//1樹高
S[root2] = root1;
}else{
if(S[root1] == S[root2]) S[root2]--; //樹高加一
S[root1] = root2;
}
同理很容易得出根據樹的規模進行歸併的程式碼:
if(S[root1] < S[root2]){//1規模大
S[root1] += S[root2]; //更新規模
S[root2] = root1;
}else{
S[root2] += S[root1];
S[root1] = root2;
}
這兩種方式都屬於 按秩歸併,按秩歸併最壞情況下的樹高是O(logN),那麼N次歸併,時間複雜度就是O(N*logN)
除了這樣按秩歸併的優化外,我們還可以壓縮路徑。
這樣使得查詢更快,因為壓縮路徑之後樹高明顯降低了。
具體程式碼:
//路徑壓縮
int myFind(int x)
{
if(S[x] < 0) return x;
return S[x] = myFind(S[x]);
}
是一種尾遞迴的方式來寫的,雖然壓縮路徑的時候會稍微花一些時間,但是之後的查詢速度快了很多。
數學可以證明,無論N有多大(int範圍內),樹高不會超過4。那麼N次歸併,時間複雜度就是O(N*一個常數),相當於O(N)。不過這道題不進行路徑壓縮也可以通過。
- 只按秩歸併(根據樹高)的程式碼
#include<stdio.h>
#include<string.h>
#define maxn 10001
int S[maxn]; //S[i]中,負數表示高度,即S[root]=-樹高,正數表示父結點
int connected;
int myFind(int x)
{
int parent = x;
while((parent=S[x]) > 0)
{
x = parent;
}
return x;
}
void myUnion(int x1, int x2)
{
int root1 = myFind(x1);
int root2 = myFind(x2);
if(root1 != root2)
{
if(S[root1] < S[root2]){//1樹高
S[root2] = root1;
}else{
if(S[root1] == S[root2]) S[root2]--; //樹高加一
S[root1] = root2;
}
connected--;
}
}
int main()
{
//freopen("set.txt","r",stdin);
int N;
char op;
int num1,num2;
int root1,root2;
scanf("%d",&N);
memset(S,-1,sizeof(int)*N);
connected = N;
while(1)
{
scanf("%c", &op);
if(op == 'S') break;
else if(op == 'I'){
scanf("%d%d",&num1,&num2);
myUnion(num1,num2);
}else if(op == 'C'){
scanf("%d%d",&num1,&num2);
root1 = myFind(num1);
root2 = myFind(num2);
if(root1 == root2) printf("yes\n");
else printf("no\n");
}
}
if(connected == 1){
printf("The network is connected.\n");
}else{
printf("There are %d components.\n",connected);
}
return 0;
}
- 既按秩歸併(根據樹的規模),也壓縮路徑的程式碼
#include<stdio.h>
#include<string.h>
#define maxn 10001
int S[maxn]; //S[i]中,負數表示高度,即S[root]=-規模,正數表示父節點
int connected;
//路徑壓縮
int myFind(int x)
{
if(S[x] < 0) return x;
return S[x] = myFind(S[x]);
}
void myUnion(int x1, int x2)
{
int root1 = myFind(x1);
int root2 = myFind(x2);
if(root1 != root2)
{
if(S[root1] < S[root2]){//1規模大
S[root1] += S[root2]; //更新規模
S[root2] = root1;
}else{
S[root2] += S[root1];
S[root1] = root2;
}
connected--;
}
}
int main()
{
//freopen("set.txt","r",stdin);
int N;
char op;
int num1,num2;
int root1,root2;
scanf("%d",&N);
memset(S,-1,sizeof(int)*N);
connected = N;
while(1)
{
scanf("%c", &op);
if(op == 'S') break;
else if(op == 'I'){
scanf("%d%d",&num1,&num2);
myUnion(num1,num2);
}else if(op == 'C'){
scanf("%d%d",&num1,&num2);
root1 = myFind(num1);
root2 = myFind(num2);
if(root1 == root2) printf("yes\n");
else printf("no\n");
}
}
if(connected == 1){
printf("The network is connected.\n");
}else{
printf("There are %d components.\n",connected);
}
return 0;
}
一般來說,按規模歸併與壓縮路徑配合使用更方便,想一想為什麼?