引言
已知有十台电脑{1,2,3,4,5,6,7,8,9,10},任给其中两台电脑,我们该如何判断这两台电脑有没有形成连接?
解决办法:我们可以将这十台电脑看做10个集合{1},{2},{3},{4},{5},{6},{7},{8},{9},{10}。将两台电脑连接起来的行为可以抽象为将两台电脑对应的集合合并,判断两台电脑是否连接只需要看他们是否属于同一个集合就可以了。
1.并查集基础版
1.2并查集的表示
通过引言可知,我们为了解决问题抽象出的集合有合并和查找两种操作。这种集合就是并查集。
并查集可以用树的结构来表示,树的每个节点就代表一个集合的元素。可以使用数组作为存储形式。代码表示如下:
typedef struct {
int Data;
int Parent;
}SetType;
其中Parent为某一结点的父节点在数组中的下标,如果该节结点没有子结点的话那么其Parent设为-1,用来表示这是一个根节点。Data用来存放数据(比如语言中的电脑序号)。
1.2并查集的操作
并查集的操作有合并和查找两种。
查找操作:
//查找某个元素所在的位置
int Find(SetType S[], int X) {
int i;
for (i = 0; S[i].Data != X && i < MaxSize; i++);
if (i >= MaxSize) return -1;//未找到X返回-1
for (; S[i].Parent >= 0; i = S[i].Parent);
return i;//返回根节点的位置
}
合并操作:
//集合的并运算
void Union(SetType S[], int X1, int X2) {
int Root1;
int Root2;
Root1 = Find(S,X1);
Root2 = Fing(S,X2);
if (Root1 != Root2) S[Root1].Parent = Root2;
}
2.并查集的简化表示
上面给出的并查集的查找操作需要先找到被查元素X在数组中的位置,再通过X的位置找出其父节点的位置并返回,这样,如果对N个元素进行查找操作,那么操作的时间复杂度就是 O ( x n ) O(x^n) O(xn)。显然,这是有点慢的。
假设集合中的元素都是整数,那么每个元素和其在数组中的位置就可以形成一对一的映射。这样,可以用数组下标加一来表示对应的元素,之后数组中存放的数据是其父节点在数组中的下标,这样集合中元素的结构就得到了简化(由一个结构体变为了整数)。但是,这种形式只有当集合中的元素可以抽象为整型变量且每个元素在数组中唯一的时候在可以使用。
这种简化版的并查集代码如下:
typedef int ElementType;//默认元素可以用非负整数表示
typedef int SetName;//用根节点的下标作为集合名称
typedef ElementType SetType[MaxSize];//数组
SetName Find(SetType S, ElementType X) {
for (; S[X] > 0; X = S[X]);
return X;
}
SetName Union(SetType S, SetName Root1, SetName Root2) {
if (Root1 != Root2) {
S[Root1] = Root2;
}
}
3.并查集练习题
对应的是浙江大学陈越何钦铭老师《数据结构》课程中关于并查集部分的练习题,题目描述在第P63。代码是自己跟着视频敲的。
#include<stdio.h>
#include<malloc.h>
#define MaxSize 1000
typedef int ElementType;//默认元素可以用非负整数表示
typedef int SetName;//用根节点的下标作为集合名称
typedef ElementType SetType[MaxSize];//数组
SetName Find(SetType S, ElementType X) {
for (; S[X] >= 0; X = S[X]);
return X;
}
//按秩归并
void Union(SetType S, SetName Root1, SetName Root2) {
if (Root1 < Root2) S[Root2] = Root1;
else {
if (Root1 == Root2) S[Root2]--;
S[Root1] = Root2;
}
}
void Initialization(SetType S, int n) {
for (int i = 0; i < n; i++) {
S[i] = -1;
}
}
void InputConnection(SetType S) {
ElementType u, v;
SetName Root1, Root2;
scanf_s("%d%d", &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_s("%d%d", &u, &v);
Root1 = Find(S, u - 1);
Root2 = Find(S, v - 1);
if (Root1 != Root2) printf("no");
else printf("yes");
}
void Check_Network(SetType S,int n) {
int i, counter;
counter = 0;
for (i = 0; i < n; i++) {
if (S[i] < 0) counter++;
}
if (counter == 1) printf("The network is connected");
if (counter != 1) printf("There are %d network",counter);
}
//集合的简化表示
int main()
{
SetType S;
int n;
char in;
scanf_s("%d", &n);
Initialization(S, n);
do {
scanf_s("%c", &in);
switch (in) {
case 'I':InputConnection(S); break;
case 'C':Check_Connection(S); break;
case 'S':Check_Network(S, n); break;
//default:break;
}
} while (in != 'S');
return 0;
}