并查集

145 阅读4分钟

并查集

并查集首先它是一个集合,并、查是它的操作,其中,“”是它最终的目的,“”则是生成可查集合的过程,即:合并以查找。并查集维护的是一系列的集合,而不是单独的一个集合。可以更高效率地判断集合元素是否属于同一集合的问题。

引入

有10 台电脑,分别为{1,2,3,4,5,6,7,8,9,10},现在已知下列电脑之间实现了网络连接:1-2,2-4,3-5,4-7,5-8,6-9,6-10,求解:2-7、5-9之间是否是相联通的。

如果我们将示意图画出后,其实很明显可以发现,2-7之间通过4是联通的;而5-9则不是联通的。 基于图的数据结构其实可以通过深度优先搜索、广度优先搜索等方法判断节点是否联通,但是现在可以给出一种新的思路:并查集。

思路

  1. 将所有的电脑单独地看作一个集合:{1},{2},{3},{4} ……;
  2. 对于已知的网络连接,我们将两个集合合并
  3. 查询X、Y是否连接的,就等价于查询X、Y是否同属于一个集合。

集合的表示

集合可以用树结构表示集合,每一个树节点即一个集合内元素,集合的指针指向树根,引例中,构建出如下的数据结构: 其中的S1、S2、S3是三个集合,如果我们要判断节点2属于哪一个集合,只需要查找节点2的树根,直到节点1,即可知道节点2是属于集合S1的。

那么如何表示一棵树呢?树的传统表示方法有以下几种:

兄弟孩子表示法

这种表示法采用两个引用域,其一指向孩子,另一个指向兄弟。不适合并查集的逆方向查找。

孩子表示法

这种表示法适合从树根向树只查找,也不适合逆向查找。

class Node<T>{
    T value;
    List<Node<T>> children;

    public Node(T value) {
        this.value = value;
        this.children = new LinkedList<>();
    }
}

双亲表示法

双亲表示法中,树的孩子节点直接指向双亲,并且采用数组即可存储,对于以上的例子,我们可以列出如下的左边的数组:Data即为所有的电脑的编号,而Parent则是其父亲节点的下标,例如电脑2,则它的父亲是电脑1,对应的Parent下标是0,如果是树根则Parent设置为1即可。根节点Parent则置-1。

这种表示法中,父亲节点没有记录下其子节点的信息,而是通过子节点来记录父节点的信息,非常适合逆方向的查找。

实际执行

问题:有10 台电脑,分别为{1,2,3,4,5,6,7,8,9,10},现在已知下列电脑之间实现了网络连接:1-2,2-4,3-5,4-7,5-8,6-9,6-10,求解:2-7、5-9之间是否是相联通的。

思路:我们只需要进行集合合并,再判断2、7以及5、9是否有公共的祖先即可;

根据上面的问题,我们先作集合合并,首先拿到第一个网络连接:1-2,1作为根节点,2作为1的孩子,更新数组,如下:

下标DataParent
01-1
120
23-
34-
45-
56-
67-
78-
89-
910-

拿到2-4后,我们先检查2,4,其中4是单独的集合元素不做处理;然后2已经和1合并过了,其1是集合S1的根,我们要将1-2、4合并,那么就要将4也挂在树根上,更新数组: 网络连接:1-2,2-4,3-5,4-7,5-8,6-9,6-10,求解:2-7、5-9之间是否是相联通的。

根据上面的问题,我们先作集合合并,首先拿到第一个网络连接:1-2,1作为根节点,2作为1的孩子,更新数组,如下:

下标DataParent
01-1
120
23-
340
45-
56-
67-
78-
89-
910-

以此类推,构建数组,直到构建完成。

以此类推,构建数组,直到构建完成,之后按照Data列访问,找到各自的祖先,如果相等则说明二者是联通的,可达的(例如2-7);如果祖先不相等则说明二者是不连通的(5-9)。

优化思路

我们一开始就是简单粗暴的把p所在的树接到q所在的树的根节点下面,那么这里就可能出现「头重脚轻」的不平衡状况,比如下面这种局面: 所以,我们希望,将小树添加到大树底下,而不是将大树贴在小树下,以减小树高。对于集合S1、S2、S3来说,我们新建一个数组来记录各自的尺寸。合并时通过比较尺寸来选择谁作为树根。

参考来自

1.中国大学慕课·浙江大学
2.知乎·通俗易懂地讲解《并查集》