分析并查集的时间复杂度
并查集的时间复杂度
其实自从高二开始学习并查集就发现这个数据结构真的奇妙,能解决许多问题,但是对时间复杂度就不太理解,大二退役之后我开始想去知道为什么是这个奇妙的时间复杂度,遂有此文。
本文参考了:OI-Wiki,coursera上的算法课程
这里证明并没有引入势能分析,但是相关东西异曲同工。
引入
并查集是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。
并查集支持两种操作:
- 合并(Union):合并两个元素所属集合(合并对应的树)
- 查询(Find):查询某个元素所属集合(查询对应的树的根节点),这可以用于判断两个元素是否属于同一集合
朴素算法
查询:我们需要沿着树向上移动,直至找到根节点。
合并:要合并两棵树,我们只需要将一棵树的根节点连到另一棵树的根节点。
那么这样的时间复杂度有多少呢?
排名(Rank)
对于并查集的中的每个树,其中
- 如果
没有叶节点 ,则 - 如果
有叶节点 ,则
某个节点到根结点的路径上的边数的最大值为排名(Rank)
注意:下面所有秩都和
那么就有以下性质:
- 对于任意节点
, 只会增加 - 在合并时,只有根节点的
才有可能增加 - 在一条路径(从下至上)中
严格递增
rank 的引理 1
对于任意的查询Find操作,该操作中生成的图
证明引理 1
假设一:如果两个的不相同的点的
相同,那么他们的子树不可能有重合部分。- 反证:如果有重合部分,那么一定有一个路径
, 为重合部分, 为两个 rank 相同的点,那么可以得出 和 之间有更大的祖先,那么就有更大的 ,与假设不符。
- 反证:如果有重合部分,那么一定有一个路径
假设二:
的子树的元素 (数学归纳法)
证明:Base:
inductive step:
不一样的子树大小,
不变一样的子树大小,
合并后,Union(x,y)得到了新祖先
现在我们来证明引理 1:n 个元素,我们设每个
我们两边取倒数,并且同时乘一个 n,那么可以得出
时间复杂度
证明了这么多,我们也能看出来
首先,
那么我们可以得到查询和合并的时间复杂度为
路径压缩+启发式合并(按秩合并)
上面的朴素算法已经很快了,但是考虑一种可能,如果这个不是一个正常的树,而是一条链,而我们正常的去查询最底下的节点,那么复杂度将会退化成
路径压缩:查询过程中经过的每个元素都属于该集合,我们可以将其直接连到根节点以加快后续查询。
在合并时我们又想到了这两个的
启发式合并(按秩合并):合并时,选择哪棵树的根节点作为新树的根节点会影响未来操作的复杂度。我们可以将节点较少或深度较小的树连到另一棵,以免发生退化。
另一种做法则是使用“秩”来比较树的大小。”秩“的定义如下:
- 只有根节点的树(即只有一个元素的集合),秩为0;
- 当两棵秩不同的树合并后,新的树的秩为原来两棵树的秩的较大者;
- 当两棵秩相同的树合并后,新的树的秩为原来的树的秩加一。
容易发现,在没有路径压缩优化时,树的秩等于树的深度减一。在有路径压缩优化时,树的秩仍然能反映出树的深度和大小。在合并时根据两棵树的秩的大小,决定新的根节点,这被称作按秩合并。
Theorem
注意:经过路径压缩过时,
内容:通过“按秩合并”和“路径压缩”优化后,进行
证明
首先我们来定义一个
很显然这些块只会有
我们给每个节点分类,我们现在称一个节点
或者 的父节点为根节点- 父亲的
比自己的 大。 注意:
如果两个条件都不符合,那么就是坏节点。
我们会尽量将一个节点变成好节点。
第一个要点:要点:每次查找操作只会访问 $O(\log^ n)
结论:在
(访问好节点的次数)- 访问坏节点的总次数,下面就来论证。
首先我们思考,在路径压缩中,其父节点被更改为具有严格更大
总时间复杂度:
我们对一个秩块
对于每个最终秩处于此块的
具有最终
因此,此秩块中对“坏节点”的访问总次数最多为
因为只有
总时间复杂度:
$O((m + n) \log^ n)
Tarjan’s Bound
该证明在1975年由Tarjan完成,后在1989年,由Fredman证明该结构下无法找到更好的时间复杂度
内容:通过“按秩合并”和“路径压缩”优化后,进行
阿克曼函数
需要两个自然数作为输入值,输出一个自然数,它的输出值增长速度非常高。
定义:
由于这个函数的增加速率非常快,那么它的反函数就相应的增长的非常慢。
反函数的定义:对于每个
证明
我们定义
显然,
这里有一个性质:只要
证明:
其中
这里我们继续定义好节点与坏节点:
一个点为好的节点当且仅当符合下面几个条件,我们称这个节点为坏节点:
不是根节点- 它的父亲不是根节点
存在祖先 使得
否则,
同 Hopcroft-Ullman 类似,在一次路径中最多有
那么最终
下面我们来计算一下这个坏节点的个数,我们同样只先看一次路径的:
路径压缩:
中间这个过渡我们用到了上面
假设
为什么?我们不从理性的(数学的)角度去分析,而是去从感性的角度,首先想
那么
那最多可以加多少次 1 呢?
那么
那么我们开始算总共访问坏节点的次数:
我们来解释一下这个东西怎么来的,在
那么我们能得出总共的时间复杂度为:
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!