并查集和代码演示

并查集的概念

在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。

例如:有十台电脑{a,b, c, d, e, f, g, h, i, j},现按顺序将a和b、c和e、d和h、h和i、f和j、e和f、a和g连通,问d和i、c和j是否连通。

解决思路:将十台电脑看成是个十个单元素的集合,将连通的合并为一个集合,最后通过判断是否在同一集合,就可以判断是否连通。

这里可以得到{a,b,g}、{c, e, f, j}、{d, h, i}三个集合,因此d和i、c和j是连通的。

集合的表示

可以采用树的形式(双亲表示法:孩子指向父亲结点),只需保存父亲结点是谁,不需要保存孩子结点,所有根节点相同的都是同一个集合中的。
1.用指针实现
2.采用数组的形式(更简单方便,查找效率更高)

这里就用数组的表示方法:

//数组中存储的结点结构:
struct SetType{
	ElementType Data;  //存储的数据
	int Parent;     //指向的父亲结点,-1表示是根节点
}

用数组0~9来存储电脑a-j, 并将数组的Parent初始化为-1,即单元素就是一个集合。

下标DataParent
0a-1
1b-1
2c-1
3d-1
4e-1
5f-1
6g-1
7h-1
8i-1
9j-1

集合的操作

集合的操作分为两种:查找(Find)、合并(Union)。

查找(Find):

我们是通过判断根节点是否相同来判断两个元素是否在同一集合中的,因此查找根节点就很重要。

int Find_1(SetType S[], ElementType X){
	//在数组S中查找值为X的元素所属的集合
	int i;
	for(i=0; i<MAX_of_S && S[i].Data!=X; i++); //MAX_of_S数组的最大长度,到元素X
	if(i>=MAX_of_S) return ERROR;   //如果没找到,返回ERROR-1
	for(; S[i].Parent>=0; i=S[i].Parent);  //找到根节点
	return i;  //返回根节点的下标
}

Find_1函数每次都要遍历的查找X在哪个位置,其实这一步可以简化,因为我们知道a-j就对应0-9,因此我们就用下标0-9来表示a-j,这样数组中就可以只存Parent,查找X在哪个位置也可以省去:

任意有限集合的(N个)元素都可以被一一映射为整数0~N-1

下标Parent
0(a)-1
1 (b)-1
2 (c)-1
3 (d)-1
4(e)-1
5 (f)-1
6(g)-1
7(h)-1
8 (i)-1
9 (j)-1
int Find_2(SetType S[], int X){
	//在数组S中查找值为X(下标)的元素所属的集合
	if(X>=MAX_of_S) return ERROR;   //如果X超过数组长度,返回ERROR-1
	for(; S[X]>=0; X=S[X]);  //找到根节点
	return X;  //返回根节点的下标
}

Find_2函数还可以进一步优化,压缩路径:因为在查找根节点的过程中,有可能会出现一棵非常高的树,那么循环的次数就会很多,但是通过压缩路径,让循环的次数变少:

int Find_3(SetType S[], int X){
	//在数组S中查找值为X(下标)的元素所属的集合
	if(X>=MAX_of_S) return ERROR;   //如果X超过数组长度,返回ERROR-1
	if(S[X]<0) return X;
	else{
		S[X]=Find(S, S[X]);  //将X的值变成根节点的值
		return S[X];  //返回根节点下标
	}
}

合并(Union):

void Union_1( SetType S, SetName Root1, SetName Root2 ) { 
	// 这里默认Root1和Root2是不同集合的根结点 
	S[Root1]=Root2;  //把Root2树加到Root1后
}

Union_1函数在合并时可能会造成生成一颗很高的树,比如:Union_1(S, 0, 1)、Union_1(S, 1, 2)、Union_1(S, 2, 3),这样就会成012的树,所以Union_1可以优化,通过按秩归并

按秩归并有两种方式:按照树高、按照树的规模
这里采用按照树的规模,也就是树有多少子节点,这样树的节点个数可以存在根节点上,根节点初始存的是-1,后面就存负的节点个数。两个集合通过比较根节点的值大小来决定谁加到谁后面。

void Union_2( SetType S, SetName Root1, SetName Root2 ){ 
	// 这里默认Root1和Root2是不同集合的根结点 
    // 保证小集合并入大集合 
    if ( S[Root2] < S[Root1] ) { // 如果集合2比较大 
        S[Root2] += S[Root1];     //集合1并入集合2 
        S[Root1] = Root2;
    }
    else {                         // 如果集合1比较大 
        S[Root1] += S[Root2];     // 集合2并入集合1  
        S[Root2] = Root1;
    }
}

总结

在使用时,可以将Find_3和Union_2搭配起来使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

积木41

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值