讲并查集之前,先给大家一个并查集模板的题目;
题目链接
最简单版的并查集
之前看别的blog的时候,看到过一个有趣的比喻,假设开始的每个人都自立帮派,每个人就是一个帮派,他就是这个帮派的代表,
假设下图每个元素都代表一个个人,即帮派代表和帮派(相当于初始化每个元素的父节点就是自己)。
然后元素1和元素3两个帮派进行决斗,每个帮派派出自己的代表作战,即1和3,比试结果是1赢了,3输了,所以1帮派和3帮派合并,此时的帮派代表变成了1.
假如此时的2想和3决斗,但是这是帮派间的决斗,所以要派出帮派的代表,即变成了1和2的决斗,结果1赢了2,1把2收入麾下,此时2所在帮派加入了1所在的帮派。
假设4和5和6同理,最后就剩下1所在和帮派和4所在的帮派了。
现在1和4所在的帮派决斗,各自派出自己的代表即宗主(也就是父节点),出战。
结果1又赢了,所以此时4所在的帮派成员全部加入到1所在的帮派。
相信看完以后就可以理解了,下面用三个函数来表示上述过程。
第一个函数是初始化每个元素的父节点为其自身。
void init(int n)//初始化,每个元素的父节点是自己
{
for(int i=1;i<=n;i++)
fa[i]=i;
}
第二个函数是查找父节点的函数。
int findx(int x)//找到父节点
{
if(fa[x]==x)
return x;
else
return findx(fa[x]);
}
第三个函数是合并函数
void mergex(int i,int j)
{
fa[findx(i)]=findx(j);
}
以上便是简单版的并查集,时间复杂度由树的深度决定。
路径压缩版的并查集
路径压缩版的并查集,就是逐渐修改元素的父节点,最终有将所有的元素全部指向树的头节点的可能。
图形大致是这样。
那么,怎么用函数实现呢??
其实就是改了查找函数,在查找函数中不断更新父节点。
int findx(int x)//找到父节点
{
return fa[x]==x?x:(fa[x]=findx(fa[x]));
}
并查集按秩合并
下面,我问大家一个问题,对于下图,是让7合并上8,还是让8合并上7?
答案是肯定的,是让8合并上7,如果让7合并上8,无疑会增大树的深度。所以我们在合并的时候,要按秩合并。
那么这个怎么操作呢??
其实只需要弄多一个记录数的深度的数组rank,在合并时只需要比较深度的大小即可。
初始化函数如下
void init(int n)
{
for (int i = 1; i <= n; ++i)
{
fa[i] = i;
rank[i] = 1;
}
}
合并代码
void merge(int i, int j)
{
int x = findx(i), y = findx(j);
if (rank[x] <= rank[y])
fa[x] = y;
else
fa[y] = x;
if (rank[x] == rank[y] && x!=y)
rank[y]++; //如果深度相同且根节点不同,则新的根节点的深度+1
}