05-树8 File Transfer (25 point(s))

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?

Input Specification:
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 stands for stopping this case.

Output Specification:
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.

Sample Input 1:

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

Sample Output 1:

no
yes
There are 2 components.

Sample Input 2:

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

Sample Output 2:

no
no
yes
yes
The network is connected.

思路如下:

此题考查的是集合及其运算,集合的表示,可以使用如下描述:

typedef struct {
	ElementType Data;
	int Parent;
} SetType;

使用双亲表示法(即孩子指向双亲)进行表示,每一集合的根节点的Parent域存放-1以区别子节点。
在该题中可以简化集合的表示,用一数组来表示集合。
由于任何有限集合的N个元素都可以被一一映射为整数0~N-1,利用该特性我们可以将每台主机的编号与数组的下标联系起来,例如S[i]表示编号为i的主机,*(S+i)用来存储其父结点的下标信息;
解决该题的关键就是Find(查找)函数和Union(并集)函数的实现。教材中Find函数是基于线性扫描的,实现时间复杂度为O(N),在实际操作过程中需要N次调用,总开销为O(N^2),有点慢。简化表示集合后,Find函数的时间开销就要小很多;
“I”的操作就是进行主机连接,将两个主机加到一个集合中,初始化时各个主机都视作一个集合,所以需要进行并集操作,单纯地将一个集合挂靠到另一个集合上会导致树的高度越来越高。对应就是部分测试用例超时,所以要用到按秩归并。按秩归并其本身是对Union操作的改进,可按照树高度或是树的规模进行,对集合根节点处存储的数值进行改造,仍为负值,其绝对值表示集合的高度或规模。再将将小树贴到大树上或将小集贴到大集上;
再有就是路径压缩,其本身是对Find操作的改进,其操作效果是将所有的子节点指向了根节点的下标,具体步骤为 1)先找到根;2)把根变成X的父结点;3)再返回根,可将查找操作时间开销变为常数,原开销为O(logN)。具体详解如图:
在这里插入图片描述
代码如下:

#include <stdio.h>
constexpr auto MaxSize = 10000;

typedef int ElementType; /*默认元素可以用非负数表示*/
typedef int SetName; /*默认用根节点的下标作为集合名称*/
typedef ElementType SetType[MaxSize];

SetName Find(SetType S, ElementType X) {
	/*默认集合元素全部初始化为-1*/
	for (; S[X] >= 0; X = S[X]);
	return X;
	/*路径压缩:先找到根;把根变成X的父结点;再返回根
	操作效果是将所有的子节点指向了根节点的下标
	Ackermann函数,时间复杂性为O(1)小于4
	该递归为伪递归,编译器在编译时将其转化为循环*/
	/*if (S[X] < 0) return X;
	else return S[X] = Find(S, S[X]);*/
}

void Union(SetType S, SetName Root1, SetName Root2) {
	/*这里默认Root1和Root2是不同集合的根节点*/
	/*单纯将Root2集合合并到Root1集合*/
	//S[Root2] = Root1;
	/*按秩归并:最坏情况下树高O(logN)
	可以按照树高进行,也可以按照规模大小进行*/
	/*1、比树高,将小树贴到大树上*/
	/*if (S[Root1] < S[Root2])
		S[Root2] = Root1;
	else {
		if (S[Root1] == S[Root2]) S[Root2]--;
		S[Root1] = Root2;
	}*/
	/*2、比规模,将小集贴到大集上*/
	if (S[Root2] < S[Root1]) {
		S[Root2] += S[Root1];
		S[Root1] = Root2;
	}
	else {
		S[Root1] += S[Root2];
		S[Root2] = Root1;
	}
}

void Input_connection(SetType S) {
	ElementType u, v;
	SetName Root1, Root2;
	scanf("%d %d\n", &u, &v);
	Root1 = Find(S, u - 1);
	Root2 = Find(S, v - 1);
	if (Root1 != Root2)
		Union(S, Root1, Root2);
}

void Check_connection(SetType S) {
	ElementType u, v;
	SetName Root1, Root2;
	scanf("%d %d\n", &u, &v);
	Root1 = Find(S, u - 1);
	Root2 = Find(S, v - 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);
}

void Initialization(SetType S, int n) {
	for (int i = 0; i < n; i++) S[i] = -1;
}

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;
}

测试结果:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值