本文主要介绍解决
动态连通性
一类问题的一种算法,使用到了一种叫做并查集的数据结构,称为
Union-Find
。
更多的信息可以参考
Algorithms
一书的
Section 1.5
,实际上本文也就是基于它的一篇读后感吧。
原文中更多的是给出一些结论,我尝试给出一些思路上的过程,即为什么要使用这个方法,而不是别的什么方法。我觉得这个可能更加有意义一些,相比于记下一些结论。
关于动态连通性
我们看一张图来了解一下什么是动态连通性:
假设我们输入了一组整数对,即上图中的
(4, 3) (3, 8)
等等,每对整数代表这两个
points/sites
是连通的。那么随着数据的不断输入,整个图的连通性也会发生变化,从上图中可以很清晰的发现这一点。同时,对于已经处于连通状态的
points/sites
,直接忽略,比如上图中的
(8, 9)
。
动态连通性的应用场景:
-
网络连接判断:
如果每个
pair
中的两个整数分别代表一个网络节点,那么该
pair
就是用来表示这两个节点是需要连通的。那么为所有的
pairs
建立了动态连通图后,就能够尽可能少的减少布线的需要,因为已经连通的两个节点会被直接忽略掉。
-
变量名等同性
(
类似于指针的概念
)
:
在程序中,可以声明多个引用来指向同一对象,这个时候就可以通过为程序中声明的引用和实际对象建立动态连通图来判断哪些引用实际上是指向同一对象。
对问题建模:
在对问题进行建模的时候,我们应该尽量想清楚需要解决的问题是什么。因为模型中选择的数据结构和算法显然会根据问题的不同而不同,就动态连通性这个场景而言,我们需要解决的问题可能是:
-
给出两个节点,判断它们是否连通,
如果连通,不需要给出具体的路径
-
给出两个节点,判断它们是否连通,
如果连通,需要给出具体的路径
就上面两种问题而言,虽然只有是否能够给出具体路径的区别,但是这个区别导致了选择算法的不同,本文主要介绍的是第一种情况,即不需要给出具体路径的
Union-Find
算法,而第二种情况可以使用基于
DFS
的算法。
建模思路:
最简单而直观的假设是,对于连通的所有节点,我们可以认为它们属于一个组,因此不连通的节点必然就属于不同的组。随着
Pair
的输入,我们需要首先判断输入的两个节点是否连通。如何判断呢?按照上面的假设,我们可以通过判断它们属于的组,然后看看这两个组是否相同,如果相同,那么这两个节点连通,反之不连通。为简单起见,我们将所有的节点以整数表示,即对
N
个节点使用
0
到
N-1
的整数表示。而在处理输入的
Pair
之前,每个节点必然都是孤立的,即他们分属于不同的组,可以使用数组来表示这一层关系,数组的
index
是节点的整数表示,而相应的值就是该节点的组号了。该数组可以初始化为:
for(int i = 0; i < size; i++)
id[i] = i;
即对于节点
i
,它的组号也是
i
。
初始化完毕之后,对该动态连通图有几种可能的操作:
-
查询节点属于的组
数组对应位置的值即为组号