带路径压缩的并查集

如字面理解,并是合并,查是可查,也就是这种构建方法可以很轻易的实现合并和查找几个元素是否属于同一个集合。

原理如下:

首先有若干个元素,每个元素之间可能存在着两两之间的直接联系,也可能存在间接联系,但只要存在联系就属于同一个集合

这可以形象化的理解为族谱/联盟,每个元素可能是另一个元素的祖先/首领(直接联系),也可能是另一个元素的隔代祖先/首领的首领(间接联系),但他们都属于一个家族/联盟,当然两个元素可能没有任何直接或间接的关系,即属于不同的集合。而并查集的目的,就是为了高效建立和区分这些元素之间的所属关系。

通常情况下,我们只知道元素两两之间的关系,同时即使两个元素属于一个集合,他们可能也不存在直接的联系,例如给出某父系族谱关系:

  1. D的父亲是B;
  2. E的父亲是C;
  3. B的父亲是A;
  4. C的父亲是A;

或许人很容易能看出来,D,E都是A的孙子,ABCDE属于一个家族,但是计算机在只知道条件1234的时候,很难判定他们是属于同一个家族。用枚举判断可能是一个选择,但是效率会十分低下,这个时候就需要更高效的办法。

具体思路是,我们首先假设ABCDE都不是其他人的儿子,他们只和自己是一个家族的,即(对应后文init函数),然后依次根据条件来建立两两之间的父子关系(后文join函数)。我们给每个人两个编号,第一个编号用来表示他自己,第二个编号用来表示直接和他联系的人,即他的父亲,那么在一开始的情况下,D的编号就是(D,D)。

读取条件1后,D的编号就变成了(D,B),表示D的父亲是B;
读取条件2后,E的编号变为(E,C);
读取条件3时,B的编号变为(B,A);
读取条件4时,C的编号变为(C,A);

那么当计算机需要知道D和E是否属于一个家族的时候,可以依据D的第二个编号找到B,再从B的第二个编号找到A,同理从E的第二个编号找到C,再从C的第二个编号找到A,来确定D和E都是A的后代,从而确定他们属于一个家族(后文find函数)。

再精益求精一些,当我们确定了D是A的后代之后,我们可以让D的第二个编号直接改为A,这样在读取条件的过程中将D,E和A直接对应,在最后需要判断D,E是否属于同一个家族的时候,可以直接找到A的身上。

同理如果A这个时候变成了X的儿子,可以让D,E直接和X对应,这样就避免了中间的递归过程,当链比较长的时候,能有效缩短时间,即路径压缩(因为是需要往祖先的祖先判断,所以在find函数内)。

上述这样的一种处理集合关系的方式,就是并查集。

图表化理解:

用表格和图形描述如下:
在这里插入图片描述

代码实现:

代码实现就按照以上的思路(这里介绍路径压缩后的思路)

这完全可以当成模板去记

用fa[]数组中的下标来表示其本身的编号,值表示其的祖先

init函数
void init()
{
	for(int i=1;i<=maxn;i++)
	{
		fa[i]=i;
	} 
}
//初始化,每个元素一开始只与自己属于同一个集合,即自己的祖先是自己;
//fa[]数组用来对每个元素赋予2个编号
find函数
int find(int x)
{
	return x==fa[x]  ?  x  :  fa[x]=find(fa[x]); 
	// 判断x==fa[x]是否城里,成立的话执行冒号“:”左边,否则执行右边
} 
//fa[x]==x即最原始的祖先结点;
//find(fa[x])是往上找祖先; 
//fa[x]=find(fa[x]) 就是路径压缩(缩链)的过程
join函数
void join(int x,int y)
{
	fa[find(x)]=find(y);
	//读入条件表示x,y有关系,则让x的祖先和y的祖先一致
}

此篇仅为本人自我学习时的笔记记录,水平并不高,只是想在刚刚学习之后进行总结,或许能帮助解答同样刚学习的人的一些困难,如果对各位有所帮助那再好不过。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值