浅谈并查集

本文介绍了并查集这一数据结构,它用于维护一个森林,每个集合由一个代表元素表示。文章详细阐述了并查集的基本操作,包括查找代表元素和合并集合,以及通过路径压缩进行效率优化。并提供了并查集的初始化、查找和合并的代码示例,同时推荐了几个相关的编程题目供读者实践。
摘要由CSDN通过智能技术生成

并查集是一种十分常用的数据结构,在本文中,我们将浅析并查集。他有许多应用的例子,最经典的就是最小生成树算法。(有兴趣同学自己百度)

何谓并查集?

并查集是树(不知道“树”的概念的童鞋请去百度),更准确的说,是一个森林 。并查集的作用,就是维护一个森林,或者说是集合。

而为了方便操作,一本给每一个集合设定一个“代表元素”,你可以直接把一个集合理解成一个集团,这个“代表元素”就是这个集团的大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));
        }
};

推荐题目:

P3367 【模板】并查集

P1551 亲戚

P1111 修复公路

好了,本文至此就全部结束了,希望能给你带来帮助。喜欢的童鞋别忘了点个赞。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值