并查集/LeetCode 990题

博客介绍了并查集的概念,用于判断两节点是否存在关系,通过树状结构实现。还提及路径压缩算法以提高查找效率,阐述了并查集的算法思路,包括初始化、查找和合并函数。最后以LeetCode 990题为例展示其应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

并查集/LeetCode 990题

并查集

并查集在网上已经有非常多的介绍了,这里就简单的说一些概念,贴上基本的函数模型和算法框架。

概念:

用来判断两个节点之间是否存在关系。通过树桩结构,一个节点只需要直到自己上面的节点是谁即可,根节点的上面一个节点是自己。当两个节点对应同一个根节点的情况就说明这两个节点是可以通过一定的路径连接在一起的。
首先int pre[]数组对应的就是每个节点的上一层节点,也就是pre[15]=4;代表的就是15节点的上一层节点是4。当pre[n]=n;的时候就说明已经找到了根节点。那么寻找根节点的代码就如下:

int find(int x)                        //查找x的根节点
{
    int r=x;
    while(pre[r] != r)                 //如果r的上级不是r(或者不是你初始化的值)
        r = pre[r] ;                   // r 接着找他的上级,直到找到根节点为止。
	return  r ;                        //找到了就返回
 }

那么联通两个节点就只要把它们的根节点连接起来就可以了,两个节点之中选一个作为另一个节点的根节点即可:

void join(int x,int y)                     //我想让x和y产生联系
{
    int fx=find(x), fy=find(y);             //找到两个节点的根节点
    if(fx != fy)                             
        pre[fx]=fy;                       //选一个作为另一个的根节点
}

路径压缩算法:

可以看到像上面这样生成的树状结构是无法预测的,很可能生成一条线性的树桩结构,导致查找效率是很低的。
所以在查询的过程中,把所以相关的节点直接聚集到根节点之下,这样只要查找很少的次数就可以知道跟节点是谁了。

算法思路:

一般来说,并查集对应三个内容:初始化,查找根节点的函数,合并集合的函数

初始化:

给每个单个数据都建立一个单独的集合
在每一个单个的集合里面,有三个东西。
①集合所代表的数据(这个初始值根据需要自己定义,不固定) ;
②这个集合的层次通常用rank表示,用于合理化树桩结构,如果不需要可以不要这个数组。(一般来说,初始化可以将每一个集合里的rank置为1);

③这个集合的类别pre(其实就是一个指针,用来指示这个集合属于那一类,合并过后的集合,他们的pre指向的最终值一定是相同的);

初始化的时候,每一个集合的pre都是这个集合自己的标号。没有跟它同类的集合,那么这个集合的源头只能是自己了。

这是最简单的并查集的情况,复杂的并查集可以再增加一些像pre这样的指针,例如对于一个动物的并查集,还可以增加代表食物的指针等。
对于更加简单的一些情况,可以就用0作为pre数组的初始化的值,也就是当pre[i]=0的时候就代表找到了根节点,但是要确定在这种情况下,0的值一定是无意义的。

int pre[max];   //集合index的类别,或者用parent表示
int rank[max];  //集合index的层次,通常初始化为0
int data[max];  //集合index的数据类型 
//初始化集合
void Make_pre(int i)
{    
    pre[i]=i;   //一个集合的pre都是这个集合自己的标号。没有跟它同类的集合,那么这个集合的源头只能是自己了。 
    rank[i]=0;
}

查找函数
int Find_pre(int i)
{
    //如果集合i的父亲是自己,说明自己就是源头,返回自己的标号
   if(pre[i]==i)
       return pre[i];
    //否则查找集合i的父亲的源头
    return  Find_pre(pre[i]);       
}

合并函数

假设需要合并的两个集合分别是x和y,根据上面说的只要找到一个选择一个成为另一个的根节点即可。为了使合并完后的树左右子树的深度差尽可能小,对于每个元素x,其rank[x]代表的是以x为根节点的子树的深度。合并时,如果rank[x]<rank[y],则另parent[x]=y,即y为x的根节点,否则则相反。

void Union(int i,int j)
{
    i=Find_pre(i);
    j=Find_pre(j);
    if(i==j) return ;
    if(rank[i]>rank[j]) pre[j]=i;
    else
    {
        if(rank[i]==rank[j]) rank[j]++;  
        pre[i]=j;
    }
}

简单的并查集的概念和函数框架就是上面这样,用LeetCode 990题来作为例子来使用一下:
在这里插入图片描述

class Solution {
public:
    int pre[256]{0};//初始化,用0作为标记。
    
    int Find_pre(int i)//查找根节点函数
    {
        if(pre[i]==0)
            return i;
        return  Find_pre(pre[i]);       
    }
    void Union(int i,int j)//合并函数
    {
        i=Find_pre(i);
        j=Find_pre(j);
        if(i==j) return ;
        if(i<j)
            pre[j]=i;
        else
            pre[i]=j;
    }

    bool equationsPossible(vector<string>& equations) {
        for(int i=0;i<equations.size();i++){
            char f=equations[i][0];
            char t=equations[i][3];
            if(equations[i][1]=='=' && Find_pre(f)!=Find_pre(t)){
                Union(f,t);
            }
        }
        for(int i=0;i<equations.size();i++){
            char f=equations[i][0];
            char t=equations[i][3];
            if(equations[i][1]=='!' && Find_pre(f)==Find_pre(t)){
                return false;
            }
        }
        return true;
    }
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值