定义:
并查集的主要用来判断集合的关系,利用树来模拟集合间的关系。当元素属于同一个集合时,他们的祖先相同,否则不同。
实现方法:
定义数据结构,存储元素和指向父亲的指针。
初始化操作:将所有元素的父亲指针指向自己(也可以定义为NULL)。
集合操作:将某个元素加入到某个集合中。将需要加入集合中的元素的祖先的父亲指针指向被加入集合的祖先节点(这里比较绕,大家可以多读几遍。举个例子就是例如有节点p1和p2,现在将p2所在的集合加入到p1所在的集合中,那么就将p2集合中祖先的父亲节点指向p1所在集合的祖先)。
查找操作:查找某个集合的祖先。第一步为路径压缩操作,即将集合中的节点直接指向祖先节点。第二步为查找操作,当节点的父亲指针指向自己时,直接返回自己,否则进行递归调用。
具体代码实现:
//实现并查集
#include <stdio.h>
typedef struct Node{
//定义并查集数据结构
int num;
struct Node *father;
}Node,*Pnode;
void Init(Pnode);//初始化操作
void Union(Pnode,Pnode);//将第二个节点所在的集合加入到第一个节点所在的集合中
Pnode Find(Pnode);//查找某个节点的祖先
int main(){
Node test[10] = {
{1,NULL},
{2,NULL},
{3,NULL},
{4,NULL},
{5,NULL},
{6,NULL},
{7,NULL},
{8,NULL},
{9,NULL},
{10,NULL}
};
for (int i = 0; i < 10; ++i) {
Init(test+i);
}
//将两个不同祖先的集合合并到一起
Union(&test[0],&test[1]);//将2加入1
Union(&test[1],&test[2]);//将3加入2
Union(&test[3],&test[4]);//将5加入4
Union(&test[1],&test[4]);//将4和5加入到1
//路径压缩测试
Union(&test[5],&test[6]);//将7加入到6
Union(&test[6],&test[7]);//将8加入7
Union(&test[7],&test[8]);//将9加入8
Union(&test[8],&test[9]);//将10加入9
//测试
for (int i = 0; i < 10; ++i) {
printf("%d", Find(test+i)->num);
}
}
void Init(Pnode p){
//初始化祖先为自己
p->father = p;
}
void Union(Pnode p1,Pnode p2){
//让所有节点的祖先都直接指向自己最终的祖先,也即父辈即祖先
p1->father = Find(p1);
p2->father = Find(p2);
//让p2的祖先指向p1的祖先
p2->father->father = p1->father;
}
Pnode Find(Pnode p){
Pnode temp = NULL;
if(p->father == p){
temp = p;
}else{
//进行递归找到祖先,同时进行路径压缩
p->father = Find(p->father);
temp = p->father;
}
return temp;
}
此外并查集直接用数组会更加方便操作,大家可以自己尝试,思路一样的。
如有bug,请各位大佬批评指正~