您的位置:首页 > 其它

连通性问题的4个解决方案

2011-05-29 22:10 274 查看
问题:

一个含有N整数的序列,它们相互独立。

现在要输入若干整数对,每输入一个整数对,表示将该两个整数连。

当输入某一个整数对时,如果根据已有的连通情况,判断该对是连通的,那么就继续输入下一对。否则将该对打印出来,表示产生一个新的并集。

请编写这样的一个C算法。

分析:

我们用数组的引索表示这N个数 id
。其实这里的引索表示的是内存的不同位置。这样就把问题转换为内存位置的连通性问题。

可以用内存位置的值相等来表示这两个位置是连通的。这样就有下面的算法:

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
;

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
, 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
, 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
, 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中解决方案。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: