【并查集】File Transfer

【并查集】File Transfer

题目传送门

题目要求:

We have a network of computers and a list of bi-directional connections. Each of these connections allows a file transfer from one computer to another. Is it possible to send a file from any computer on the network to any other?

输入格式:

Each input file contains one test case. For each test case, the first line contains N (2≤N≤10​4​​ ), the total number of computers in a network. Each computer in the network is then represented by a positive integer between 1 and N. Then in the following lines, the input is given in the format:

I c1 c2

where I stands for inputting a connection between c1 and c2; or

C c1 c2

where C stands for checking if it is possible to transfer files between c1 and c2; or

S

where S stands for stopping this case.

输出格式:

For each C case, print in one line the word “yes” or “no” if it is possible or impossible to transfer files between c1 and c2, respectively. At the end of each case, print in one line “The network is connected.” if there is a path between any pair of computers; or “There are k components.” where k is the number of connected components in this network.

输入样例1:

5
C 3 2
I 3 2
C 1 5
I 4 5
I 2 4
C 3 5
S

输出样例1:

no
no
yes
There are 2 components.

输入样例2:

5
C 3 2
I 3 2
C 1 5
I 4 5
I 2 4
C 3 5
I 1 3
C 1 5
S

输出样例2:

no
no
yes
yes
The network is connected.

解题思路:

用并查集的思想,先让每一台电脑都作为一个独立的集合,然后按照用户输入进行连接和检查,连接就是集合的合并,检查就是是否为同一集合的检查。

随着集合的合并,树的高度会不断增加,这会导致查询(给定元素查询所在的集合)时间变长,常规的集合合并(随意将root1树合并到root2树上或者2合并到1上)会运行超时,需要进行优化。优化的方法是按秩归并。

按秩归并的思路:将矮树合并到高树上,这样树的高度不会增减,如果两个树的高度一致,树高才会加一,S[root]原本存储-1,用来表示它是根结点,可以利用该位置,将树高信息放在这个位置。可以让S[root] = -树高,这样可以直接从树根结点得到树的高度信息。

注意:
  1. for循环如果作为一条单独的语句,一定一定要加分号!!!不加分号编译器不会报错,但会导致逻辑错误,极难发现!!!!
  2. 如果输入有问题或者应该输出的地方没有输出,而是延后输出,应该格外注意\n的问题。
完整程序:
/*
【注意】
1.for循环如果作为一条单独的语句,一定一定要加分号!!!不加分号编译器不会报错,但会导致逻辑错误,极难发现!!!!
2.如果输入有问题或者应该输出的地方没有输出,而是延后输出,应该格外注意\n的问题
*/
#include <stdio.h>
#define MaxSize 100000


typedef int ElementType; // 元素类型为正整数(输入为从1开始编号的N台电脑)
typedef int SetName; // 集合名用根结点下标表示
typedef ElementType SetType[MaxSize]; // 集合

void Initialization(SetType S,int N); // 集合初始化(一开始所有的电脑都是一个孤立的集合)
void Input_connection(SetType S); // 建立连接
void Check_connection(SetType S); // 检查连接
void Check_network(SetType S,int n); // 检查局域网个数(集合个数)
void Union(SetType S,SetName root1,SetName root2); // 合并两个集合
SetName Find(SetType S,ElementType e); // 查找元素所在集合

int main()
{
    SetType S;
    int N;
    char in;
    scanf("%d\n",&N);
    Initialization(S,N);
    do{
        scanf("%c",&in);
        switch(in){
        case 'I':Input_connection(S);break;
        case 'C':Check_connection(S);break;
        case 'S':Check_network(S,N);break;
        }
    } while(in!='S');
    return 0;
}

void Initialization(SetType S,int N)
{
    int i;
    for(i = 0;i < N;i ++) // 将1-N编号的电脑一一映射
        S[i] = -1;  // 所有的结点都是一个独立的集合
}

void Input_connection(SetType S)
{
    ElementType n1,n2; // 两台电脑编号
    SetName root1,root2; // 两台电脑对应的集合名(用根结点下标表示)
    scanf("%d %d\n",&n1,&n2);
    root1 = Find(S,n1 - 1); // 因为编号是1-N,将其映射为0-N-1
    root2 = Find(S,n2 - 1);
    if(root1 != root2) // 如果两台电脑不属于一个集合
        Union(S,root1,root2); // 就将其并为一个集合
}

void Check_connection(SetType S)
{
    ElementType n1,n2;
    SetName root1,root2;
    scanf("%d %d",&n1,&n2); // 注意:这里不能加\n,因为这里回车结束后需要直接输出结果,但是如果用\n则需要额外输入一个非空白符才会结束
    root1 = Find(S,n1 - 1);
    root2 = Find(S,n2 - 1);
    if(root1 == root2)
        printf("yes\n");
    else
        printf("no\n");
}

void Check_network(SetType S,int n)
{
    int i,counter = 0; // 根结点数,即集合数
    for(i = 0;i < n;i ++) {
        if(S[i] < 0)    counter ++;
    }
    if(counter == 1)
        printf("The network is connected.\n");
    else
        printf("There are %d components.\n",counter);
}

// 并查集的优化:按秩归并
// 如果不优化,则是随意合并树,例如将root1树合并到root2树上
// 如果树太高,Find函数的查询效率会很低,因为如果要查询树底的元素所在的集合,就需要一层一层向上查,效率低下
// 按秩归并的思路:将矮树合并到高树上,这样树的高度不会增减,如果两个树的高度一致,树高才会加一
// 可以让S[root] = -树高,这样可以直接从树根结点得到树的高度信息
void Union(SetType S,SetName root1,SetName root2)
{
    if(S[root1] < S[root2]) // 注意因为是负数,所以root1树比root2树高
        S[root2] = root1;
    else {
        if(S[root1] == S[root2]) // 如果两棵树等高,则合并后树高一定加一
            S[root2] --; // 因为是负数,所以其实是树高加一
        S[root1] = root2;
    }
}

// 层层查找时间代价高,由于Union函数进行了优化(对树高进行了优化),所以树结构较为合理,因此该方法时间效率不会很低,所以也是合理的
// 如果要优化该方法,需要使用路径压缩
SetName Find(SetType S,ElementType e)
{
    // 集合元素默认初始化为-1
    for(;S[e] >= 0;e = S[e]); // 逐层向上查找根结点
    return e;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百栗.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值