并查集是一种十分常用的数据结构,在本文中,我们将浅析并查集。他有许多应用的例子,最经典的就是最小生成树算法。(有兴趣同学自己百度)
何谓并查集?
并查集是树(不知道“树”的概念的童鞋请去百度),更准确的说,是一个森林 。并查集的作用,就是维护一个森林,或者说是集合。
而为了方便操作,一本给每一个集合设定一个“代表元素”,你可以直接把一个集合理解成一个集团,这个“代表元素”就是这个集团的大BOSS。
并查集一般支持如下几种操作:
1.查抄一个元素所在集合的代表元素。
2.将两个集合合并。
集合怎么办,用STL的set?非也!我们直接存储为一棵树,每个集合就是一棵树,集合的“代表元素”就是这棵树的根节点。
对于点,我们只需要存储它的父节点。这样就很好办了,即
int f[MAXN];
对于每个集合,我们先初始化为一个元素,也就是他本身。所以他就是这个集团的大BOSS,他就是自己的父节点。所以,我们只要把f数组的每一个元素初始化为他的下标就好了。
这是并查集的初始化:
void init(int x)
{
n=x;
for(int i=1;i<=n;++i)
f[i]=i;
}
很简单,是吧?
初始化后,集合就建立好了。举个例子,十个集合,在初始化后是这个样子:
呃,我画图不行,请谅解。不过这张图应该很直观了。
那么,怎么查找一个集合的代表元素呢?(如何求一棵树的根节点)
其实我们用暴力方法就好了,直接每个节点向上找,如果找的一个节点f[x]==x
那么,就证明他就是这棵树的根节点(没有父亲节点了)。还有一种优化,会在后面讲。
举个例子(我用一颗已经画好的树):
我们想求蓝色节点所在树的根节点,那么只要按照黄色的路径就行了。也就是,每次跳到这个节点的父节点,直到到达根节点为止。
代码如下:
int find_fa(int x)//x为当前节点
{
if(f[x]==x)//这就是根节点
return x;
return find_fa(f[x]);//跳到父节点(f[x])
}
挺简单是不?
接下来,到了最关键的部分:合并两个集合。
怎么办呢?
一种直接的想法是,把一棵树的根节点拼到另一棵树的根节点上。没错,就是这方法,大概是下图这样:
那怎么实现呢?**只要把一棵树的根节点的父节点改成另一棵树的根节点就好了!!!**也就是
f[a]=b;//其中,a为一棵树的根节点,b为另一棵树的根节点
那a和b怎么求呢?这时候,find_fa操作就派上用场了。
两个元素想要合并在同一个集合里,那就直接把他们的集合合并嘛,因为这两个元素可能不是所在集合的“代表元素”(根节点),所以要先求树的根节点,merge函数如下:
void merge(int x,int y)
{
int a=find_fa(x),b=find_fa(y);//找到所在集合(树)的“代表元素”(根节点)
if(a==b)
return;//在同一个集合就不改了
f[a]=b;
}
很简单有木有?
至此,并查集的基础就讲完了,接下来,是优化。
find_fa优化:路径压缩
find_fa很简单,但是有一根问题,在下图所示的树上,它的效率会低的不能再低:
这棵树退化成了链,如果按照老套路一个点一个点跳,肯定TLE。
那么,方法就是,我们再查找一个节点的祖先时,直接把他的父节点改成祖先,降低树的高度,如下图所示:
这样操作后,树就会变成这个样子,不提高时间复杂度。
这种优化叫做路径压缩。代码如下:
int find_fa(int x)
{
if(f[x]==x)
return x;
f[x]=find(f[x]);//路径压缩,直接把该节点的父节点改为这棵树的根节点
return f[x];//此时的父节点已经是根节点了
}
完整代码:(我封装成了一个class)
class Union_find_set
{
protected:
int f[10001];
int n;
public:
Union_find_set(int n1)
{
n=n1;
for(int i=1;i<=n;++i)
f[i]=i;
}
int find_fa(int x)
{
if(f[x]==x)
return x;
f[x]=find_fa(f[x]);
return f[x];
}
void merge(int x,int y)
{
int a=find_fa(x);
int b=find_fa(y);
if(a==b)
return;
f[a]=b;
}
bool same(int x,int y)//判断两个元素是否在同一集合中(祖先相
//同)
{
return (bool)(find_fa(x)==find_fa(y));
}
};
推荐题目:
好了,本文至此就全部结束了,希望能给你带来帮助。喜欢的童鞋别忘了点个赞。