为了说明我们设计和分析算法的基本方法,我们通过学习一个基本的例子来说明:
- 优秀的算法能够解决实际问题而变地很重要;
- 高效的算法的代码很简单;
- 理解某个实现的性能特点是一项有趣而令人满足的挑战;
- 在解决同一个问题的多种算法之间进行选择时,科学方法是一种重要的工具;
- 迭代式改进能够让算法的效率越来越高;
为解决动态连通性问题设计算法的任务转化为了实现这份API,所有的实现都应该
- 定义一种数据结构表示已知的连接;
- 基于此数据结构实现高效的union()、find()、connected()和count()方法;
因为数据结构的性质直接影响到算法的效率,因此数据结构和算法的设计是紧密相关的,在此处根据API说明,我们可以用一个以触点为索引的数组id[]作为基本数据结构来表示所有分量。我们将使用分量中的某个触点的名称作为分量的标识符,因此我们可以认为每个分量都是由它的触点之一所表示的。
对于已知信息进行扩展
- 我们有N个分量,每个触点都构成了一个只含有它自己的分量,因此我们将id[i]的值初始化为i,其中i在0到N-1之间。
- 对于每个触点i,我们将find()方法用来判断它所在的分量所需的信息保存在id[i]中。
- connected()方法的实现只用一条语句find(p)==find(q),它返回一个布尔值。
- 我们维护了两个实例变量,一个是连通分量的个数,一个是数组id[],用于储存触点,同时如果更改为别的分量时,将其中的值更改为别的分量的值。
由此我们可以知道在研究发现union-find的API的各种算法时,union-find的成本模型指的是,我们统计的是数组的访问次数(访问任意数组元素的次数,无论读写)。
实现
quick-find
一。此种方法时保证当且仅当id[p]等于id[q]时,p和q是连通的。换句话说,在同一连通分量中的所有触点在id[]中的值必须全部相同。这将意味着,connected(p,q)只需判断id[p]==id[q]。
quick-find算法分析
- 命题F:在quick-find算法中,每次find()调用只需访问数组一次,而归并两个分量的union()操作访问数组的次数在(N+3)到(2N+1)之间
- 证明:由代码可知,每次connected调用都会检查id[]数组中的两个元素是否相等,即会调用两次find()方法。归并两个分量的union()操作会调用两次find(),检查id【】数组中的全部N个数组并改变它们中1到N-1个元素的值。
假设我们使用quick-find算法来解决动态连通性问题并且最后只得到了一个连通分量,俺么这至少需要调用N-1次union(),即至少(N+3)(N-1)~N^2次数组访问----我们马上可以猜想动态连通性的quick-find算法是平方级别的。