C++并查集

并查集,大概意思上就是说个体a和个体b是否为同一个数据集中,如果不是,那么就要想办法将这两个集合合并起来,这样总的集合数目就由2变成1。在C++中,并查集的基本问题就是求一个图的连通子图数问题,具体可参考九度online Problem 1012 畅通工程问题。
该问题的主要意思是,在一个地图上有若干个城镇,每个城镇可以看作是一个点,如果城镇之间可以直接或者间接的连通(例如a和b相连,b和c相连,那么a和c也是相连),这些城镇就可以看作是一个集合,如果不能连通,就属于不同的集合。问题是要求出在地图上还需要修建几条路,能够将所有的城镇都连通起来,而不需要要求具体写出到底要在哪两个城镇之间修路。
在一开始看到这个问题的时候,我的第一反应就是可以用迪杰斯特拉算法来求。将一个城镇设为起点v,如果v到其他城镇有距离,则不需要修路,如果没有,则需要修路,即修路个数+1。后来我又发现,这个不行,因为迪杰斯特拉算法只是求单源点到其它点的最短路径,例如,起点v和点w想通,k和j想通,但无论是v还是w都和k或者j都不相通,那么按照迪杰斯特拉算法,需要修两条路,其实只需要一条就行了。所以,该算法不行。
后来看了别人的评论,才发现是要求连通子图数的问题。而求解这类问题,有个最简单方便的算法,就是并查集,在网上详细了解之后,发现这个算法确实十分的高效简洁,特将个人的一点想法分享给大家。
int find(int x)
{
int r = x;
while (pre[r] != r)
{
r = pre[r];
}
int i = x, j;
while (i != r)
{
j = pre[i];
pre[i] = r;
i = j;
}
return r;
}

其中,数组Pre用来保存每个节点的父节点,find函数用来查找节点的根节点是哪个,并且,在查找的过程中,使用了路径压缩的方法,就是说,加入a的父节点是b,b的父节点是c,c的父节点是d,d为根节点,那么,a,b,c的根节点就是d。我们在find(a)的过程中,首先是找到a的根节点为d,然后进行路径压缩
int i = x, j;
while (i != r)
{
j = pre[i];
pre[i] = r;
i = j;
}

这段代码就是进行路径压缩的。首先,我们保留了a的节点信息,并赋值给i,然后开始压缩路径,因为此时a!=d,所以进行while循环,首先找到a的父节点为b并赋值给j,然后让a的父节点直接指向根节点d,这时候让a原来的父节点b赋值给i,再次判断此时有没有到a原来的根节点就是d,不是,继续进行,此时找到b的父节点为c赋值给j,接着同样将c的父节点直接指向根节点d,以此类推。直到找到原来的根节点为止。
大概意思,用更简单的例子讲,就是,d的手下是c,c的手下是b,b的手下是a,在压缩过程中,直接让a b c三个手下全部成为d的一级手下,这就是路径压缩的原理。

你可能感兴趣的:(C++学习)