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≤104), 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.
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
no
yes
There are 2 components.
Sample Input 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
Sample Output 2:
no
no
yes
yes
The network is connected.
这题在mooc上陈越老师的课上说过思路,大致来说就是:将一台电脑1和另一台电脑2相连,相当于这两台电脑处于同一个集合内,并且由于这个连线是双向的,也就是说1可以通过这个连线与和2相连的其他电脑连接起来,同理,2也是这样。
所以换个说法就是,1所在的集合和2所在的集合并在了一起,而要搜索两个电脑是不是连在一起,要跟着连线走,就相当于在寻找根节点的过程,那就是用树来将集合并起来,也就是并查集问题。
怎么用树将集合并起来呢,那么就是将两个数的根节点连在一起,而这道题中,如果采用一般方法归并的话,会出现运行超时的问题,因为如果一直是要将树中最下面的结点和新的结点(电脑)相连,需要不停地从最下面往上走找到根,第N个元素找N遍,时间复杂度为O(n^2)。
所以在课上,老师就介绍了按秩归并的方法,分为两种(按树的高度、按元素的个数)。也就是说要么将矮的树并到高的树里,要么将元素个数小的归并到元素个数大的树里。在用数组表示树的时候,我们将结点序号设为数组下标,其中的值便是其父节点的下标,那么根节点的值一般是设为-1(因为没有父节点),而按秩归并中,为了方便的表示,若用树的高度来分,其数组的值为-h(h为树的高度),若用元素个数来分,其值为-n(n为元素个数)。
对于C语言来说,因为输入输出很快,不像java语言一样,其实可以对运行时间提出更高的要求,而为了得到运行效率更高的代码,老师又介绍了路径压缩的方法。
int Find(int S[], int x) //路径压缩
{
if(S[x]<0) return x;
else return S[x]=Find(S, S[x]);
}
上述代码带来的结果是将所有节点都直接连上了根节点,这样直接减少了寻找根节点的时间。运行时间又得到了提高。
而用路径压缩时,如果采用树的高度来归并,需要函数返回一次,就修改一次树的高度,会非常麻烦,所以一般都是将路径压缩和按元素个数归并结合起来使用。总体代码为:
//05-树8 File Transfer (25 分)
#include<stdio.h>
#define Maxsize 10000
int S[Maxsize];void Initialize(int N);
void check(void);
void Union(void);
int Find(int S[], int x);
void judge(int N);int main()
{
int N=0, i=0;
char In;
scanf("%d", &N);
Initialize(N);
do{
scanf("%c", &In);
switch(In){
case 'C': check(); break;
case 'I': Union(); break;
case 'S': judge(N); break;
}
} while(In!='S');
return 0;
}void Initialize(int N) //初始化
{
for(int i=0;i<N;i++){
S[i]=-1;
}
}void check(void)
{
int c1, c2;
int root1, root2;
scanf("%d%d", &c1, &c2);
root1=Find(S, c1-1);
root2=Find(S, c2-1);
if(root1==root2) printf("yes\n");
else printf("no\n");
}void Union(void)
{
int c1, c2;
int root1, root2;
scanf("%d%d", &c1, &c2);
root1=Find(S, c1-1);
root2=Find(S, c2-1);
if(S[root1]<S[root2]){
S[root1]=S[root2]+S[root1]; //按秩归并(元素个数)
S[root2]=root1;
}
else {
S[root2]=S[root2]+S[root1];
S[root1]=root2;
}
}int Find(int S[], int x) //路径压缩
{
if(S[x]<0) return x;
else return S[x]=Find(S, S[x]);
}void judge(int N)
{
int cnt=0, i=0;
for(i=0;i<N;i++){
if(S[i]<0) cnt++;
}
if(cnt==1) printf("The network is connected.");
else printf("There are %d components.", cnt);
}