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?

Input Specification:

Each input file contains one test case. For each test case, the first line contains N (2), 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.

 

集合的定义和并查操作

  这道题目需要用到集合去解决,下面先给出集合的一般表示方法,以及并查操作。

 1 // 集合的表示 
 2 struct SetType {
 3     int data;
 4     int parent; 
 5 };
 6 
 7 // 集合的查找操作
 8 int findRoot(SetType *set, int x) {
 9     int i = 0;
10     // MAXSIZE是全局变量,为集合的最大长度 
11     while (i < MAXSIZE && set[i].data != x) {
12         i++;
13     }
14     
15     if (i >= MAXSIZE) return -1;
16     
17     while (set[i].parent >= 0) {
18         i = set[i].parent;
19     }
20     
21     return i;
22 }
23 
24 // 集合的并操作
25 void UnionSet(SetType *set, int x1, int x2) {
26     int root1 = findRoot(set, x1);
27     int root2 = findRoot(set, x2);
28     
29     // 这里对集合的表示进行改进,集合的根的parent值不再用-1表示,而是用该集合所包含元素个数的负数来表示
30     // 同时,把包含元素少的集合并到元素多的集合,这样就可以防止集合的高度过高,从而提高了查找的效率 
31     if (root1 != root2) {
32         if (set[root1].parent > set[root2].parent) {// 第一个集合的根的parent值小于第二个集合的根的parent值,意味着第一个集合的元素个数少于第二个集合 
33             set[root2].parent += set[root1].parent; // 先更新第二个集合中的元素个数,也就是两个集合元素的总个数 
34             set[root1].parent = root2;              // 把第一个集合并到第二个集合 
35         }
36         else {                                      // 否则,第一个集合的根的parent值大于第二个集合的根的parent值,意味着第二个集合的元素个数少于第一个集合
37             set[root1].parent += set[root2].parent; // 先更新第一个集合中的元素个数,也就是两个集合元素的总个数
38             set[root2].parent = root1;              // 把第二个集合并到第一个集合 
39         }
40     }
41 }

   给出一个例子方便理解:

File Transfer

 

 解题思路

   File Transfer这道题目需要用到集合来解决。不过我们可以对集合的定义进行简化。

  我们并不需要把电脑的编号作为集合元素里的data,而是直接把电脑编号映射为数组的下标,同时数组里面直接存放它的parent,这样就无需定义集合这个结构体,用一个数组就可以解决了。同时,在查找操作中,我们直接通过下标来找到这个元素的位置,从而减少一次用来查找元素位置的循环。

  所以我们程序的框架就是,先让用户输入计算机的个数,来建立一个对应大小的集合。然后根据用户输入的指令来进行一系列对应的操作。其中查找和合并操作和上面的代码大同小异。

  下面给出AC代码:

 1 #include <cstdio>
 2 #include <iostream>
 3 
 4 void unionSet(int *set);
 5 void check(int *set);
 6 int findRoot(int *set, int num); 
 7 
 8 int main() {
 9     int n;
10     scanf("%d", &n);
11     
12     int set[n + 1];                         // 直接把电脑编号映射为数组的下标,所以数组长度要+1 
13     set[0] = n;                             // set[0]用来记录当前独立集合的个数,一开始每一个元素都是一个独立的集合,初始化为n 
14     std::fill(set + 1, set + n + 1, -1);    // 每一个元素都是单独的一个集合,为每个元素的parent值初始化为-1,表示该集合中就只有这一个元素 
15     
16     char ch;
17     while ((ch = getchar()) != 'S') {       // 当用户的输入不为'S',持续循环读入用户的操作 
18         if (ch == 'I') unionSet(set);
19         else if (ch == 'C') check(set);
20     }
21     
22     if (set[0] == 1) printf("The network is connected.");    // 当set[0] == 1表示所有的元素都属于同一个集合 
23     else printf("There are %d components.", set[0]);         // 当set != 1表示这些元素构成的集合个数多于1,不连通 
24     
25     return 0;
26 }
27 
28 void unionSet(int *set) {
29     int num1, num2;
30     scanf("%d %d", &num1, &num2);
31     int root1 = findRoot(set, num1);
32     int root2 = findRoot(set, num2);
33     
34     // 合并的操作与之前的一样,把元素个数少的集合并到元素个数多的集合中 
35     if (root1 != root2) {
36         if (set[root1] > set[root2]) {
37             set[root2] += set[root1];
38             set[root1] = root2; 
39         }
40         else {
41             set[root1] += set[root2];
42             set[root2] = root1;
43         }
44         
45         set[0]--;    // 每一次合并表示一个集合变成了另外一个集合的元素,所以集合的个数要-1 
46     }
47 }
48 
49 void check(int *set) {
50     int num1, num2;
51     scanf("%d %d", &num1, &num2);
52     int root1 = findRoot(set, num1);
53     int root2 = findRoot(set, num2);
54     
55     printf("%s\n", root1 == root2 ? "yes" : "no");
56 }
57 
58 int findRoot(int *set, int num) {
59     // 查找变得很容易,只要发现set[num] < 0就说明找到该集合的根了 
60     while (set[num] > 0) {
61         num = set[num];
62     }
63     
64     return num;
65 }

  其中在合并的操作中不一定要按照集合元素个数的大小来合并,也可以按照集合的高度来合并,把高度小的并到高度大的,这样就可以防止树的高度增加。

File Transfer

  对应的unionSet函数修改如下:

 1 void unionSet(int *set) {
 2     int num1, num2;
 3     scanf("%d %d", &num1, &num2);
 4     int root1 = findRoot(set, num1);
 5     int root2 = findRoot(set, num2);
 6     
 7     if (root1 != root2) {
 8         // 注意数组中下标为根存放的值是负数 
 9         // 合并的规则是把小树贴到大树上 
10         if (set[root1] > set[root2]) {  // 说明root2对应的树高大于root1对应的树高 
11             set[root1] = root2;         // 把第一个集合并到第二个集合
12         }
13         else {                          // 否则,root1对应的树高大于root2对应的树高 
14             // 如果root1对应的树高与root2对应的树高相同,则root1对应的树高+1 
15             if (set[root1] == set[root2]) set[root1]--;
16             set[root2] = root1;         // 把第二个集合并到第一个集合
17         }
18     }
19 }

  我们这里仍然采用按规模大小来合并的规则,是为了下面实现路径压缩这一操作。

 

 算法改进

  我们来对查找函数来进行改进,来减少查找的次数。这种方法就叫路径压缩,具体代码如下:

1 int findRoot(int *set, int num) {
2     if (set[num] < 0) return num;
3     else return set[num] = findRoot(set, set[num]);
4 }

  这是用递归实现的,如果set[num] < 0,说明num就是根的下标,我们返回即可。如果不是,我们先递归去找到num所属集合的根,再把set[num]赋值为找到的集合的根,也就是说在数组中,下标为num直接存放集合的根,这样下次再查找num时只用查找1次就可以了。

  这个递归过程是这样的:

File Transfer

   最后附上改进后的AC代码:

File Transfer
 1 #include <cstdio>
 2 #include <iostream>
 3 
 4 void unionSet(int *set);
 5 void check(int *set);
 6 int findRoot(int *set, int num); 
 7 
 8 int main() {
 9     int n;
10     scanf("%d", &n);
11     
12     int set[n + 1];
13     set[0] = n;
14     std::fill(set + 1, set + n + 1, -1);
15     
16     char ch;
17     while ((ch = getchar()) != 'S') {
18         if (ch == 'I') unionSet(set);
19         else if (ch == 'C') check(set);
20     }
21     
22     if (set[0] == 1) printf("The network is connected.");
23     else printf("There are %d components.", set[0]); 
24     
25     return 0;
26 }
27 
28 void unionSet(int *set) {
29     int num1, num2;
30     scanf("%d %d", &num1, &num2);
31     int root1 = findRoot(set, num1);
32     int root2 = findRoot(set, num2);
33     
34     if (root1 != root2) {
35         if (set[root1] > set[root2]) {
36             set[root2] += set[root1];
37             set[root1] = root2; 
38         }
39         else {
40             set[root1] += set[root2];
41             set[root2] = root1;
42         }
43         
44         set[0]--;
45     }
46 }
47 
48 void check(int *set) {
49     int num1, num2;
50     scanf("%d %d", &num1, &num2);
51     int root1 = findRoot(set, num1);
52     int root2 = findRoot(set, num2);
53     
54     printf("%s\n", root1 == root2 ? "yes" : "no");
55 }
56 
57 int findRoot(int *set, int num) {
58     if (set[num] < 0) return num;
59     else return set[num] = findRoot(set, set[num]);
60 }
View Code

File Transfer

 

参考资料

  浙江大学——数据结构:https://www.icourse163.org/course/ZJU-93001?tid=1461682474

上一篇:LeetCode-两棵二叉搜索树的所有元素


下一篇:左神算法进阶班1_2判断两个树的结构是否相同