連通性問題的4個解決方案

問題:

一個含有N整數的序列,它們相互獨立。

現在要輸入若干整數對,每輸入一個整數對,表示將該兩個整數連。

當輸入某一個整數對時,如果根據已有的連通情況,判斷該對是連通的,那麼就繼續輸入下一對。否則將該對打印出來,表示產生一個新的並集。

請編寫這樣的一個C算法。

 

分析:

我們用數組的引索表示這N個數 id[N]。其實這裏的引索表示的是內存的不同位置。這樣就把問題轉換爲內存位置的連通性問題。

可以用內存位置的值相等來表示這兩個位置是連通的。這樣就有下面的算法:

    while(scanf("%d %d/n", &p, &q) == 2){
        if(id[p] == id[q]) continue;
        for(t = id[p], i = 0; i < N; ++i)
            if(id[i] == t) id[i] = id[q];
        printf(" %d %d/n", p, q);
    }

遍歷整個數組,然後將所有等於id[p]的位置的值設置爲id[q]這樣就實現了並集。

 

快速查找算法和測試函數如下:

//創建連通樹,輸入對時,打印出未連通的。
#include<stdio.h>
#define N 10
void quick_find( int *id );
int main(){
    //不同的內存位置來存放整數,那麼兩個內存位置的值相同表示這兩個內存位置連通。
    int i, id[N];
    for(i = 0; i < N; ++i){
        id[i] = i;
        printf(" %d ", id[i]);
    }
    printf("/n");
    quick_find(id);
}

void quick_find( int *id ){
    int i, p, q, t;
    //輸入的兩個值表示
    while(scanf("%d %d", &p, &q) == 2){
        if(id[p] == id[q]) continue;
        //並集操作
        for(t = id[p], i = 0; i < N; ++i)
            if(id[i] == t) id[i] = id[q];
        for(i = 0; i < N; ++i){
            printf(" %d ", id[i]);
        }
        printf("/n");
    }
}

關於這裏的快速查找算法,我們傳入的是數組,其實就是對這樣的一個數組作一個並集操作,並且輸入要連通的位置,然後打印出這個數組沒每一次並集後的狀態,這裏暫且不分析它的時間複雜度。但是可以想一下,每一次並集合都要遍歷整個數組。能不能不要這樣遍歷數組呢?

 

下面是快速並集的算法:

        for(i = p; i != id[i]; i = id[i]);
        for(j = q; j != id[j]; j = id[j]);
        if(i == j) continue;
        id[i] = j;//i指向j,連接兩個根節點。

當輸入兩個位置後,先要找到它的根節點,當兩個跟節點不相等的時候,讓i指向j,即將j作爲新的根節點。

下面是完整的代碼:

#include<stdio.h>
#define N 10
void quick_union(int *id);
int main(){
    int id[N], i;
    for(i = 0; i < 10; i++){
        id[i] = i;
        printf(" %d ", id[i]);
    }
    printf("/n");
    quick_union(id);
}
void quick_union(int *id){
    int p, q, i, j;
    while(scanf("%d %d", &p, &q)){
        //當i等於id[i]的時候,表示i位置是一個根節點。
        //這裏的for循環的判斷條件是i,j位置是否是一個根節點。最終肯定能找到根節點。
        for(i = p; i != id[i]; i = id[i]);
        for(j = q; j != id[j]; j = id[j]);
        if(i == j) continue;
        id[i] = j;//i指向j,連接兩個根節點。
        for(i = 0; i < 10; i++){
            printf(" %d ", id[i]);
        }
        printf("/n");
    }
}

這裏的快速並集算法,之所以叫做快速並集算法,我可以看到當我們進行並集操作的時候,只需要把輸入位置的兩個根節點作合併爲一個根節點。顯然這裏的查找過程是緩慢的。

當我們把最後的兩個根節點合併爲一個根節點的時候,可以考慮兩個節點的權重問題。

        if(sz[i] < sz[j]){
            id[i] = j;//當i節點的權重小時,讓i指向j
            sz[j] += sz[i];

        }else{
            id[j] = i;
            sz[i] += sz[j];
        }

 

下面是完整的加權快速並集算法和測試的代碼:

 

#include<stdio.h>
#define N 10
void quick_union(int *id);
int main(){
    int id[N], i;
    for(i = 0; i < 10; i++){
        id[i] = i;
        printf(" %d ", id[i]);
    }
    printf("/n");
    quick_union(id);
}
void quick_union(int *id){
    int p, q, i, j;
    while(scanf("%d %d", &p, &q)){
        //當i等於id[i]的時候,表示i位置是一個根節點。
        //這裏的for循環的判斷條件是i,j位置是否是一個根節點。最終肯定能找到根節點。
        for(i = p; i != id[i]; i = id[i]);
        for(j = q; j != id[j]; j = id[j]);
        if(i == j) continue;
        if(sz[i] < sz[j]){
            id[i] = j;//當i節點的權重小時,讓i指向j
            sz[j] += sz[i];

        }else{
            id[j] = i;
            sz[i] += sz[j];
        }

        for(i = 0; i < 10; i++){
            printf(" %d ", id[i]);
        }
        printf("/n");
    }
}

我們可以修改樹的結構來使算法更加有效,可以採用分路徑壓縮。分路徑壓縮是很容易實現的:

可以將i指向id[i]所指向的位置。

        for(i = p; i != id[i]; i = id[i]) id[i] = id[id[i]];
        for(j = q; j != id[j]; j = id[j]) id[j] = id[id[j]];

下面是加權快速並集分路徑壓縮算法的完整測試代碼:

#include<stdio.h>
#define N 10
void quick_union(int *id);
int main(){
    int id[N], i;
    for(i = 0; i < 10; i++){
        id[i] = i;
        printf(" %d ", id[i]);
    }
    printf("/n");
    quick_union(id);
}
void quick_union(int *id){
    int p, q, i, j;
    while(scanf("%d %d", &p, &q)){
        //當i等於id[i]的時候,表示i位置是一個根節點。
        //這裏的for循環的判斷條件是i,j位置是否是一個根節點。最終肯定能找到根節點。
        for(i = p; i != id[i]; i = id[i]) id[i] = id[id[i]];
        for(j = q; j != id[j]; j = id[j]) id[j] = id[id[j]];
        if(i == j) continue;
        if(sz[i] < sz[j]){
            id[i] = j;//當i節點的權重小時,讓i指向j
            sz[j] += sz[i];

        }else{
            id[j] = i;
            sz[i] += sz[j];
        }

        for(i = 0; i < 10; i++){
            printf(" %d ", id[i]);
        }
        printf("/n");
    }
}

關於不同的解決方案的優越性,這裏不討論。這裏只是給出這3中解決方案。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章