带权并查集学习总结

带权并查集

参考博客: 并查集到带权并查集

       \space \space\space \space\space \space       我们为什么要学习带权并查集呢?我们知道,对于普通并查集而言,它反映的是集合与集合之间的关系(即元素是否处于这个集合),相当于是一个无权图,而集合内元素之间的关系并没有表明。如果我们一旦想体现出集合中元素之间的相互关系,即边权值,那么普通并查集必然不能实现,所以带权并查集就出来了。它不仅记录集合的关系,还记录着集合内元素的关系或者说是集合内元素连接线的权值。 这个本质实际上就是一个带权图。

       \space \space\space \space\space \space       那么,我们如何考虑权值呢?在普通并查集中只有两种操作,一种是路径压缩寻找根节点,一种是合并操作。 要知道,每个节点记录的是与父节点边的权值。那么在路径压缩过程中,我们对权值也应该进行相应的更新。因为在路径压缩之前,每个节点都是与其父节点链接着,那个权值自然也是与其父节点之间的权值,而路径压缩之后,父节点早已改变成根节点,这是我们最值得注意的一步操作,这也是带权并查集最难理解的地方。在合并操作中同样如此,我们需要将两个节点连接起来,那么一个节点的父节点自然改变了,我们同样也要进行权值更新。

       \space \space\space \space\space \space       所以我们需要在普通并查集的基础上添加一个 v a l u e value value数组来表示

路径压缩

这里我们如何更新权值呢?我们想想,我们在未进行路径压缩之前,是这样的一个情况: A − > B − > C − > D − > E A->B->C->D->E A>B>C>D>E假设 E E E就是根节点,我们需要从 A A A点开始进行路径压缩,而每个节点它们的权值只是到它们当前父节点的权值。 我们要的是每个节点的父节点都是根节点,那么权值也是与根节点的权值。故这里我们需要如何处理呢?我们看直接与根节点 E E E相连的 D D D点,这个 D D D点的权值自然是没有必要更新,就是 v a l u e [ D ] , value[D], value[D]那么 C C C点呢?它所要进行的更新操作是不是 v a l u e [ C ] = v a l u e [ C ] + v a l u e [ D ] value[C]=value[C]+value[D] value[C]=value[C]+value[D],而 B B B点呢?若 C C C点的权值已经更新,即说明 C C C点是与根节点 E E E相连,那么是不是也是一样的方法,直接加上上一个点的权值即可。所以我们发现了什么,这是一个递归的过程,先寻找到根节点,再回溯更新权值 和根节点。 故非递归版路径压缩在这里是不太好用的,我们使用递归版路径压缩。具体看代码。

int Find(int x){
    
    
	//这里要使用递归去更新带权值。
	if(x==father[x]){
    
    
		return x;
	}
	else{
    
    
		int temp=father[x];//记录原来父节点的编号。
		father[x]=Find(father[x]);//继续递归下去找到根节点,再回溯更新带权值,这个递归结束后说明value[x]已经是与根节点的权值了。
		value[x]+=value[temp];
		return father[x];//返回根节点。
	}
}

合并

已知x,y根节点分别为xRoot,yRoot,如果有了x、y之间的关系,合并如果不考虑权值直接修改father就行了,但是现在是带权并查集,必须得求出xRoot与yRoot这条边的权值是多少,很显然x到yRoot两条路径的权值之和应该相同,就不难得出上面代码所表达的更新式。我们来看几张图。

在这里插入图片描述
那么合并操作就很好写了,我们来看代码。

int xRoot = FindSet(x);
int yRoot = FindSet(y);
if (xRoot != yRoot){
    
    
	father[xRoot] = yRoot;
	value[xRoot] = -value[x] + value[y] + s;
}

这就是带权并查集,当然,用起来可并不是这么简单,我们怎么去表示这个权值这是一个问题,不要套模板,得用心去体会,才是解题之道。

猜你喜欢

转载自blog.csdn.net/hzf0701/article/details/109003395