这是一个类似于对集合的操作的题
思路
- 集合的逻辑结构可以看成是一个树(逻辑结构如下图),每个集合的代表元素为这个树的根,当合并两个元素对应的集合的时候,我们需要知道这写元素对应属于哪个集合,即找到这些集合对应的代表元素(即树根),再进行合并之类的操作
- 这个树是子结点指向父结点(集合中的元素保存的是集合代表元素的下标)
. - 我们把集合建立成一个数组,因为题目中的各个结点都是连续的数,所以可以通过数组的下标来映射到各个结点的数,所以这个数组中保存的值为这个结点对应集合的代表元素的下标,而某个结点为代表元素时,保存的是这个集合全部元素的个数的负数(负数是为了辨认其为集合的代表元素,而保存集合元素个数是为了进行按秩归并)
- 我们把题目中的连接操作看成是连接两个集合的概念,每次要连接两个元素对应的集合时,我们先Find这两个元素对应集合的代表元素,比较代表元素的规模(每个集合所含元素的个数),把规模小的加到规模大的结合上(把规模小的对应的树添加到规模大的树上)
- Find函数实现了路径压缩,要查找某个元素对应集合的代表元素时,从这个结点不断向树根递归,找到树根后再一步步把树根的下标返回(让递归中的元素直接指向树根,使得检下次查这写元素时更方便)
#include <stdio.h>
#include <stdlib.h>
int Find(int S[], int X) {
if (S[X] < 0) {
return X;
}
else
return S[X] = Find(S, S[X]); //路径压缩
}
//按秩归并
void Union(int S[], int root1, int root2) { //比规模进行
if (S[root2] < S[root1]) {
S[root2] += S[root1];
S[root1] = root2;
}
else {
S[root1] += S[root2];
S[root2] = root1;
}
}
void Input_connection(int S[]) {
int u, v;
int root1, root2;
scanf("%d %d\n", &u, &v);
root1 = Find(S, u - 1);
root2 = Find(S, v - 1); //v这个元素在数组中下标为v-1的地方
if (root1 != root2)
Union(S, root1, root2);
}
void Check_connection(int S[]) {
int u, v;
int root1, root2;
scanf("%d %d\n", &u, &v);
root1 = Find(S, u - 1);
root2 = Find(S, v - 1); //v这个元素在数组中下标为v-1的地方
if (root1 == root2)
printf("yes\n");
else
printf("no\n");
}
void Check_network(int 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 Init(int S[], int n) {
for (int i = 0; i < n; i++) {
S[i] = -1;
}
}
int main() {
int S[10000]; //对于结点-1的为在数组中下标,而其中的值为父结点的下标,父结点的值为集合元素的个数的负值
int n;
char in;
scanf("%d\n", &n);
Init(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;
}