算法竞赛——并查集

并查集

在学习并查集之前我们需要弄懂什么是并查集,首先引入一个问题,给定一些元素以及元素之间的相互关系,如何快速判断两个元素是否在一个集合里。给个更具体的例子,如果两个人之间有共同的好友我们就说这两个人是好友,如小明和小红是好友,小红和小黄是好友,那么小明和小黄也是好友。并查集就是用来解决这样一类问题的森林数据结构,假如小明和小红是好友那么他们之间就会在同一颗树上。
在这里插入图片描述
它支持两种操作:
查找(Find):确定某个元素处于哪个子集;
合并(Union):将两个子集合并成一个集合。

查找

并查集实现查找的思路可以类比一个故事:几个家族进行宴会,但是家族普遍长寿,所以人数众多。由于长时间的分离以及年龄的增长,这些人逐渐忘掉了自己的亲人,只记得自己的爸爸是谁了,而最长者(称为「祖先」)的父亲已经去世,他只知道自己是祖先。为了确定自己是哪个家族,他们想出了一个办法,只要问自己的爸爸是不是祖先,一层一层的向上问,直到问到祖先。如果要判断两人是否在同一家族,只要看两人的祖先是不是同一人就可以了。
初始化定义数组fa[idx]=idx,表示每个人都代表独立的一个节点,也就是自己是自己的祖先`

// C++ Version
void makeSet(int size) {
  for (int i = 0; i < size; i++) fa[i] = i;  // i 就在它本身的集合里
  return;
}

查找函数

// C++ Version
int fa[MAXN];  // 记录某个人的爸爸是谁,特别规定,祖先的爸爸是他自己

// 递归
int find(int x) {
  // 寻找x的祖先
  if (fa[x] == x)  // 如果 x 是祖先则返回
    return x;
  else
    return find(fa[x]);  // 如果不是则 x 的爸爸问 x 的爷爷
}

// 非递归
int find(int x) {
  while (x != fa[x])  // 如果 x 不是祖先,就一直往上一辈找
  {
    x = fa[x];
  }
  return x;  // 如果 x 是祖先则返回
}

路径压缩

我们可以发现如果多次查找相同的问题,答案是相同的,我们并不在乎查找的路径中发生了什么事情,只在乎查找的起点和查找的终点是谁,于是我们可以进行路径压缩。把在路径上的每个节点都直接连接到根上,这就是路径压缩。具体用代码该如何实现呢?大家可以暂停一会先行思考一下这个问题。
图示:
在这里插入图片描述
代码
其实道理很简单,最终find一定会返回一个根节点,那么递归找到根节点后,让每个节点都等于根节点,就完成了路径压缩

// C++ Version
int find(int x) {
  if (x != fa[x])  // x 不是自身的父亲,即 x 不是该集合的代表
    fa[x] = find(fa[x]);  // 查找 x 的祖先直到找到代表,于是顺手路径压缩
  return fa[x];
}

时间复杂度与空间复杂度

通过路径压缩可以大大缩短并查集查找的时间复杂度,可近似的看为O(1),具体证明自行查阅,此处不做过多解释。
空间复杂度为O(n)

合并

第二个操作为合并,合并操作比较简单,想想我们是如何判断两个人是否在一个集合里的,只需要判断两个人的根节点是否相同,那么合并操作时,我们只需要改变其中一个根节点的祖宗节点(本来为它本身)为另外一个集合的祖宗节点即可。
在这里插入图片描述

// C++ Version
void unionSet(int x, int y) {
  // x 与 y 所在家族合并
  x = find(x);
  y = find(y);
  fa[x] = y;  // 把 x 的祖先变成 y 的祖先的儿子
}

这里大家可能有个疑问,那其它节点(属于x的子节点的),不是还没有修改吗?那么判断x的儿子和原本y的儿子,不就不相等了。这里我们要回到find函数

// C++ Version
int find(int x) {
  if (x != fa[x])  // x 不是自身的父亲,即 x 不是该集合的代表
    fa[x] = find(fa[x]);  // 查找 x 的祖先直到找到代表,于是顺手路径压缩
  return fa[x];
}

大家可以看到终止的条件为 x==fa[x],那么原本1号节点为终止节点,但是修改之后f[1]=6,所以会向上递归,最终返回6,如果是路径压缩,则还会将其它所有节点都改变为6.

题单推荐

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值