一、背景介绍
问题输入:一系列整数对,每个整数可以表示一种类型的对象;一个整数对表示两个对象之间是相连的
相连关系:一种等价关系,具有自反性、对称性,传递性
问题核心:设计数据结构保存已知整数对以及它们的相连信息,并用此数据结构判断一对新对象是否相连
为限定问题范围,现设置相应术语:对象->触点、整数对->连接、相连的对象集合->连通分量(分量)
二、算法API及数据结构设计
API设计:
*数据结构设计:众所周知,数据结构的性质将直接影响算法效率,且API已经说明触点与分量都会用int表示,所以使用以触点为索引的数组保存触点之间的连通性信息。UF算法的三种实现形式都是由于数组中保存的连通性信息不同来决定的。
三、UF算法的三种实现形式:
首先抛开实现形式,我们可以发现API中的某些方法是非常容易实现的,因此我们先给出除关键方法之外的实现:
此外我们规定该算法的成本模型为保存连通性信息的数组在算法中的被访问次数(与关键代码运行的次数为同一量级)。
1.quick-find算法
连通性信息:处于同一个连通分量的触点在数组对应索引(恰好是这些触点的值)的取值上都相同
关键算法union(p,q) 实现思路:
(i) 若触点p,q属于同一个连通分量,则它们在数组上的取值都相同,不必再修改数组的值
(ii) 从连通性信息分析可以知道,只要遍历数组,找到所有与触点p取值相同的触点,将它们在数组上的取值改为触点q的取值即可
具体实现:
算法缺点:每当执行union方法时都要遍历数组,若问题中有N个连接则算法将是平方级别的,不适合大规模的问题。
2.quick-union算法
连通性信息:数组在每个触点处的取值是该触点所连接的触点,比如id[0] = 1,表示问题中存在一个连接为(0,1); 若id[i] = i,则表示触点i为该连通分量的根结点。
关键算法find(p)实现思路:
从触点p开始随着连接一直循环直至找到该连通分量的根结点。
具体实现:
关键算法union(p,q) 实现思路:
(i) 若触点p,q属于同一个连通分量,则它们通过find方法找到的根结点相同,不必再修改数组的值。
(ii) 从连通性信息分析可以知道,只要触点p与q分别所属的连通分量的根结点,再在两个根结点之间建立一个连接,便合并了这两个连接分量。
具体实现:
算法缺点:连通树的高度称为关键性能因素:一般情况下,该算法是lgN级别的;但是在问题的最坏情况下,算法仍然是平方级别的,不适合大规模的问题。
3.加权quick-union算法
连通性信息:与quick-union算法的连通性信息保持一致,但是新增一个触点数组(以触点为索引)记录每个根结点所属的连通分量拥有的结点个数。
关键算法find(p)实现与quick-union算法一致。
关键算法union(p,q) 实现思路:
(i) 若触点p,q属于同一个连通分量,则它们通过find方法找到的根结点相同,不必再修改数组的值。
(ii) 从连通性信息分析可以知道,只要触点p与q分别所属的连通分量的根结点,再在两个根结点之间建立一个连接,便合并了这两个连接分量;但是为了避免quick-union算法中的最坏情况,限制连通树的高度,我们在连接根结点时进行判断,将触点个数较少的连通分量连接到触点个数较多的连通分量上。
限制连通树高度示意图:
具体实现:
算法分析:连通树的高度一直很小。相比于quick-union算法,在加权quick-union算法中,它的union算法与find算法在最坏情况下也始终是lgN级别的,因此加权quick-union算法在最坏情况下也仍然是线性对数级别的,具有较大的性能优势。
下面给出三种实现的性能对比:
4.路径压缩的加权quick-union算法
具体实现与加权quick-union算法一致,只是增加了路径压缩的思想。
路径压缩:在find方法检查每个触点是否是根触点的同时,将不是根触点的触点直接连接到根触点。
笔者的个人实现: