并查集

并查集主要用于实现集合的合并。一个并查集至少支持以下两种操作:

  1. 合并:将两个集合合并为一个集合
  2. 查询:查询两个元素是否在一个集合中

并查集利用树的性质实现这两种功能,每个集合都用该集合中所有元素的祖先来表示,所以用一个 fa 数组记录每个元素的祖先。初始情况下,所有元素均各自独立,即 \(fa[i]=i\),也就是一片由 \(n\) 棵大小均为 \(1\) 的树组成的森林。那么合并的时候只需要把两个集合的祖先变成同一个,查询的时候只需判断两个集合的祖先是否一致即可。现在唯一的问题是,在经过若干次修改后如何快速确定某个元素的祖先。

一个简单的想法是直接暴力跳 fa 数组,直到找到祖先就是自己的元素为止,这个元素就肯定是祖先了。但是这样复杂度不能保证。但是可以发现,这个到祖先的路径中除了祖先之外的结点是没什么用处的,所以直接把这些结点的 fa 设为祖先就好了,这就是路径压缩,可以把一条路径上的点全都直接连接到根上。

简单的实现:

int findfa(int x){
    return fa[x] == x ? fa[x] : fa[x] = findfa(fa[x]);
}

采用路径压缩优化后,findfa 操作的最坏时间复杂度是 \(O(\log n)\) 的,但它可以被进一步优化:按秩合并。按秩合并的想法也很好理解:当我们合并两个集合时,本质上是将两棵树合并为一棵,那么肯定是把小的树作为大的树的子树更合适。无论用点数还是深度来评判树的大小,同时使用路径压缩和按秩合并的时间复杂度都是 \(O(\alpha(m,n))\)\(\alpha\) 是反 Ackermann 函数)。如果不用路径压缩只用按秩合并,时间复杂度同样为 \(O(\log n)\)

此外,并查集的边上也可以定义权值,在路径压缩的过程中修改,这就是带权并查集。

posted @ 2021-09-24 14:26  Theophania  阅读(61)  评论(0)    收藏  举报