【并查集】 带权并查集详解

Contents:

一.引入

二.查询函数与集合合并函数(伪代码)

三.权值如何更新


一.引入

   从图的角度理解:

   并查集实际上是一颗由若干个树构成的森林,而如果还需要储存边的权值,则需要额外用一个数组d记录每个结点和其父节点的权值

   从集合的角度理解:

   并查集储存的信息是某点属于哪个集合,而带权并查集还能记录某点和其父结点的“关系”

   这个思路其实很简单,但是路径压缩函数和集合合并函数都需要改写:

   ①因为路径压缩会改变点与点的连接关系,路径压缩后,点的父节点变为了根节点,所以需要更新d数组

   ②集合的代表元(根节点)是没有父结点的,所以d数组中也没有值。但是合并集合后其中一个根节点变为了另一个集合根节点的子节点,所以需要赋值

二.查询函数与集合合并函数(伪代码)

  查询函数:

int root_find(int x){
   if(x==pre[x])  return x; //如果x==pre[x],说明x是根节点,根节点也不需要更新权值,直接返回x就行
   int root=root_find(pre[x]); //递归寻找根节点
   更新d[x]值;
   return pre[x]=root;  //更新父结点(路径压缩)
}

 

  说明一下递归函数的逻辑:

  它会先执行root_find()函数直至找到根节点,再回来执行当前函数中剩余没执行完的部分

  也就是在你更新d[x]值时,d[pre[x]]是已经更新完了的

 

   集合合并函数:

   根节点是不保存权值的,因为根节点没有父节点,而在树的合并时,其中一个树的根节点会变为另一个树的子结点,所以也需要为其赋上权值

void join(int x,int y){
   int rx=root_find(x);
   int ry=root_find(y);
   if(rx!=ry){
       pre[rx]=ry;
       更新d[rx]值;
   }
  
}

三.权值如何更新

  上面的函数都是用伪代码写的,因为更新权值的规则要视题目而定

  这个权值其实描述的是一种“关系”

  以POJ-1182这道很经典的食物链为例,题目规定三种物种:A吃B,B吃C,C吃A

  可以用0表示和父结点同类,1表示吃父结点,2表示被父结点吃

  则在更新权值时,可以这么写:

int root_find(int x){
   if(x==pre[x])  return x;
   int root=root_find(pre[x]);
   rela[x]+=rela[pre[x]],rela[x]%=3;
   return pre[x]=root;
}

  前面说过,因为递归函数的特性,所以: ①rela[pre[x]]必然已经被更新为pre[x]到根节点的权值了

  ②而rela[x]存放的是x到pre[x]的权值

  根据这两段已知的关系(权值)我们可以推断出x与根节点的关系

  因为这道题三者循环的克制关系,可以使用模运算进行操作

  

  集合合并会更复杂些,是由三对已知的关系推第四个已知关系:

  即已知点x和rx的关系,点y和ry的关系,点x和点y的关系,推导点rx和点ry的关系

void join(int x,int y,int r){
  int rx=root_find(x);
  int ry=root_find(y);
  if(rx!=ry){
      pre[rx]=ry;
      rela[rx]=(r+rela[y]-rela[x]+3)%3;

  }
}

 

 具体的权值更新方式还是得看题目,但是原理大致就是如此了

 

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值