并查集(Union Find)
什么是并查集
并查集是一种树型的数据结构,其保持着用于处理一些 不相交集合
(Disjoint Sets)的 合并及查询
问题。其包含合并集合
和 查找集合中的元素
两种操作。并且其常用于解决 动态连通性一类问题
实现
并查集实现有两种方式 分别是: 简单元素替换
和 代表元素合并
Quick Find
可以将该方法考虑为,为数据分为不同的集合,每次合并一个新的元素进来的时候,都要将原来集合里所有元素的值都修改为新并入元素的值。实现简单就是每次更新需要遍历一遍集合
1 | public interface UF { |
1 | public class UnionFind implements UF { |
它查找元素的时间复杂度非常低,只有O(1),而合并集合元素的时候时间复杂度达到了O(N)。这种实现看起来还可以,就是感觉合并的时候似乎慢了一点,那么有没有办法使得合并的操作快点呢?
Quick Union
和前面归并元素的思路不同,这里采用的是一种类似于树的层次结构。我们可以创建一个 parent 数组,数组元素值是其下标的父节点。按照这个思路,如果我们将一个元素并入到一个集合的时候,我们可以修改这个集合的代表元素,只要这个代表元素的值为这个并入的元素就可以了
1 | public class UnionFind2 implements UF { |
这种实现里,查找所在集合的代表元素需要遍历它的父节点,直到它和它的下标值相同。但是归并的时候只要修改一个元素的值就可以了。不过从最坏的情况来看,查找这个集合的根节点的时间复杂度就可能将近O(N)了。看来这个办法是使得归并的操作简单了,但是整体的时间复杂度并没有完全降下去。
那么还有没有什么办法可以改进呢?
优化
在前面的 代替元素合并
方法中,会有极端的情况是因为,没有合并集合时进行判断,如果集合越来越长,也就退化成为了链表。所以,我们可以在这里进行优化,将要合并的两个集合进行判断(元素数量或高度),把数值低的纳入在高的下面,可以大大提升效率
基于Size优化
基于 Size 优化并不如基于 Rank 优化效果好,而且代码也很简单,所以就不详细列出了。若有兴趣,可以观察下面的代码,进行仿写。
基于Rank优化
1 | public class UnionFind4 implements UF { |
路径压缩
经过上面的优化,效率已经有了很大的提升。在运行中,有很大的一部分时间是花在了,通过一个节点查找其根节点上。我们要是在这个时间段上,进行路径的压缩会不会更有效果那?
非递归路径压缩
1 | private int find(int p){ |
进行简单的路径压缩,只需要对 基于Rank 优化的代码中 find方法
进行改动即可
代码含义是 : 把从当前叶节点到向上每两层的节点都指向自己的祖父节点
递归路径压缩
1 | private int find(int p){ |
如此改动之后,则会将所有元素,指向根节点。
添加路径压缩后,并查集的时间复杂度是 O(log*n) ,比 O(log(n)) 还要快一点
相关问题
主要用于解决动态连接性问题,LeetCode上面有并查集的标签可以尝试一下