连通性问题的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中解决方案。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值