并查集的数据结构记录了一组分离的动态集合 S={S1,S2,S3….,Sk}。每个集合通过一个“代表”加以识别,代表即该集合的一个元素。
并查集的基本操作:
(1)Make_Set(x):建立一个新的集合,且其中仅有的元素为x。各集合是分离的,x没有在其他集合中出现过。
(2)Find_Set(x):获得x所在集合的“代表”。
(3)Union(x,y):将元素x所在集合Sx和元素y所在集合Sy合并为一个新的集合。新合并的集合的”代表”是Sx的代表或者Sy的代表。
并查集本身不具有结构,必须借助一定的数据结构加以实现。并查集的数据结构实现方法比较多,一般用的比较多的有数组实现、链表实现和树实现。下面主要介绍树实现方法。
树实现:用有根树来表示集合,树中的每个节点代表集合中的一个元素。树的根是该集合的“代表”。每个节点有一个指针指向其父亲节点,根节点的父节点指向自身。一个集合代表一个树,多个集合形成一个森林。
主要讲一下集合树的合并:显而易见,在较低的树中查找根节点比较快,所以,假如有两颗分离的集合数A和B,深度分别为ha和hb,若ha>hb,则应将B树作为A树的子树,新树的深度为ha。若ha < hb,则将A树作为B树的子树,新树的深度为hb。若ha=hb,则任意选一棵树作为另一颗的子树,新树的深度为ha+1(或hb+1)。
/*
功能:并查集的算法和实现
作者:pussy
日期:2015-12-21
*/
# include<stdio.h>
# include<malloc.h>
//并查集节点类型定义
typedef struct{
int data;//节点对应的数据
int parent;//节点的双亲下标
int rank;//节点的秩(用来表示以该节点为根节点的子树的高度)
}UFSTree;
void Union(UFSTree t[],int x,int y);
void Make_Set(UFSTree t[],int n);
int Find_Set(UFSTree t[],int x);
int main()
{
int N,M,Q;
printf("请输入人的个数和已知的亲戚对数\n");
scanf("%d%d",&N,&M);
UFSTree *t=(UFSTree *)malloc((N+1)*sizeof(UFSTree));
Make_Set(t,N);
int i,a,b;
printf("每次输入两个数(a,b),代表已知a和b是亲戚关系(格式:a b)\n");
for(i=1;i<=7;i++)
{
scanf("%d%d",&a,&b);
Union(t,a,b);
}
printf("请输入Q(询问的次数):\n");
scanf("%d",&Q);
printf("请输入您的询问,(格式:a b),若a和b是亲戚,则输出YES,否则输出NO\n");
for(i=1;i<=Q;i++)
{
scanf("%d%d",&a,&b);
int pa=Find_Set(t,a);
int pb=Find_Set(t,b);
if(pa==pb)//在同一个集合里
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
//并查集树的初始化(每个元素都是一个单独的集合)
void Make_Set(UFSTree t[],int n)
{
int i;
for(i=1;i<=n;i++)
{
t[i].data=i;
t[i].parent=i;
t[i].rank=1;
}
}
//查找一个元素所属集合
//每个集合有一个代表元素
//查找结果返回所属集合的代表元素
int Find_Set(UFSTree t[],int x)
{
while(t[x].parent!=x)
{
x=t[x].parent;
}
return x;
}
//两个元素所属的集合合并
void Union(UFSTree t[],int x,int y)
{
int t1=Find_Set(t,x);//获取x所属集合的代表元素
int t2=Find_Set(t,y);//获取y所属集合的代表元素
if(t[t1].rank>t[t2].rank)
{
t[t2].parent=t1;
}
else if(t[t1].rank<t[t2].rank)
{
t[t1].parent=t2;
}
else
{
t[t1].parent=t2;
t[t2].rank++;
//或者t[t2].parent=t1;
//t[t1].rank++;
}
}