并查集复杂度
本部分内容转载并修改自 时间复杂度 - 势能分析浅谈,已取得原作者授权同意。
定义
阿克曼函数
这里,先给出 𝛼(𝑛)
的定义。为了给出这个定义,先给出 𝐴𝑘(𝑗)
的定义。
定义 𝐴𝑘(𝑗)
为:
𝐴𝑘(𝑗)={𝑗+1𝑘=0𝐴(𝑗+1)𝑘−1(𝑗)𝑘≥1
即阿克曼函数。
这里,𝑓𝑖(𝑥)
表示将 𝑓
连续应用在 𝑥
上 𝑖
次,即 𝑓0(𝑥) =𝑥
,𝑓𝑖(𝑥) =𝑓(𝑓𝑖−1(𝑥))
。
再定义 𝛼(𝑛)
为使得 𝐴𝛼(𝑛)(1) ≥𝑛
的最小整数值。注意,我们之前将它描述为 𝐴𝛼(𝑛)(𝛼(𝑛)) ≥𝑛
,反正他们的增长速度都很慢,值都不超过 4。
基础定义
每个节点都有一个 rank。这里的 rank 不是节点个数,而是深度。节点的初始 rank 为 0,在合并的时候,如果两个节点的 rank 不同,则将 rank 小的节点合并到 rank 大的节点上,并且不更新大节点的 rank 值。否则,随机将某个节点合并到另外一个节点上,将根节点的 rank 值 +1。这里根节点的 rank 给出了该树的高度。记 x 的 rank 为 𝑟𝑛𝑘(𝑥)
,类似的,记 x 的父节点为 𝑓𝑎(𝑥)
。我们总有 𝑟𝑛𝑘(𝑥) +1 ≤𝑟𝑛𝑘(𝑓𝑎(𝑥))
。
为了定义势函数,需要预先定义一个辅助函数 𝑙𝑒𝑣𝑒𝑙(𝑥)
。其中,𝑙𝑒𝑣𝑒𝑙(𝑥) =max(𝑘 :𝑟𝑛𝑘(𝑓𝑎(𝑥)) ≥𝐴𝑘(𝑟𝑛𝑘(𝑥)))
。当 𝑟𝑛𝑘(𝑥) ≥1
的时候,再定义一个辅助函数 𝑖𝑡𝑒𝑟(𝑥) =max(𝑖 :𝑟𝑛𝑘(𝑓𝑎(𝑥)) ≥𝐴𝑖𝑙𝑒𝑣𝑒𝑙(𝑥)(𝑟𝑛𝑘(𝑥))
。这些函数定义的 𝑥
都满足 𝑟𝑛𝑘(𝑥) >0
且 𝑥
不是某个树的根。
上面那些定义可能让你有点头晕。再理一下,对于一个 𝑥
和 𝑓𝑎(𝑥)
,如果 𝑟𝑛𝑘(𝑥) >0
,总是可以找到一对 𝑖,𝑘
令 𝑟𝑛𝑘(𝑓𝑎(𝑥)) ≥𝐴𝑖𝑘(𝑟𝑛𝑘(𝑥))
,而 𝑙𝑒𝑣𝑒𝑙(𝑥) =max(𝑘)
,在这个前提下,𝑖𝑡𝑒𝑟(𝑥) =max(𝑖)
。𝑙𝑒𝑣𝑒𝑙
描述了 𝐴
的最大迭代级数,而 𝑖𝑡𝑒𝑟
描述了在最大迭代级数时的最大迭代次数。
对于这两个函数,𝑙𝑒𝑣𝑒𝑙(𝑥)
总是随着操作的进行而增加或不变,如果 𝑙𝑒𝑣𝑒𝑙(𝑥)
不增加,𝑖𝑡𝑒𝑟(𝑥)
也只会增加或不变。并且,它们总是满足以下两个不等式:
0≤𝑙𝑒𝑣𝑒𝑙(𝑥)<𝛼(𝑛)
1≤𝑖𝑡𝑒𝑟(𝑥)≤𝑟𝑛𝑘(𝑥)
考虑 𝑙𝑒𝑣𝑒𝑙(𝑥)
、𝑖𝑡𝑒𝑟(𝑥)
和 𝐴𝑗𝑘
的定义,这些很容易被证明出来,就留给读者用于熟悉定义了。
定义势能函数 Φ(𝑆) =∑𝑥∈𝑆Φ(𝑥)
,其中 𝑆
表示一整个并查集,而 𝑥
为并查集中的一个节点。定义 Φ(𝑥)
为:
Φ(𝑥)={𝛼(𝑛)×rnk(𝑥)rnk(𝑥)=0 或 𝑥 为某棵树的根节点(𝛼(𝑛)−level(𝑥))×rnk(𝑥)−𝑖𝑡𝑒𝑟(𝑥)otherwise
然后就是通过操作引起的势能变化来证明摊还时间复杂度为 Θ(𝛼(𝑛))
啦。注意,这里我们讨论的 𝑢𝑛𝑖𝑜𝑛(𝑥,𝑦)
操作保证了 𝑥
和 𝑦
都是某个树的根,因此不需要额外执行 𝑓𝑖𝑛𝑑(𝑥)
和 𝑓𝑖𝑛𝑑(𝑦)
。
可以发现,势能总是个非负数。另,在开始的时候,并查集的势能为 0
。
证明
union(x,y) 操作
其花费的时间为 Θ(1)
,因此我们考虑其引起的势能的变化。
这里,我们假设 𝑟𝑛𝑘(𝑥) ≤𝑟𝑛𝑘(𝑦)
,即 𝑥
被接到 𝑦
上。这样,势能增加的节点仅有 𝑥
(从树根变成非树根),𝑦
(秩可能增加)和操作前 𝑦
的子节点(父节点的秩可能增加)。我们先证明操作前 𝑦
的子节点 𝑐
的势能不可能增加,并且如果减少了,至少减少 1
。
设操作前 𝑐
的势能为 Φ(𝑐)
,操作后为 Φ(𝑐′)
,这里 𝑐
可以是任意一个 𝑟𝑛𝑘(𝑐) >0
的非根节点,操作可以是任意操作,包括下面的 find 操作。我们分三种情况讨论。
- 𝑖𝑡𝑒𝑟(𝑐)
和 𝑙𝑒𝑣𝑒𝑙(𝑐)
并未增加。显然有 Φ(𝑐) =Φ(𝑐′)
。
- 𝑖𝑡𝑒𝑟(𝑐)
增加了,𝑙𝑒𝑣𝑒𝑙(𝑐)
并未增加。这里 𝑖𝑡𝑒𝑟(𝑐)
至少增加一,即 Φ(𝑐′) ≤Φ(𝑐) −1
,势能函数减少了,并且至少减少 1。
- 𝑙𝑒𝑣𝑒𝑙(𝑐)
增加了,𝑖𝑡𝑒𝑟(𝑐)
可能减少。但是由于 $0
本页面最近更新:2025/5/24 12:24:08,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:orzAtalod, Enter-tainer, H-J-Granger, StudyingFather, CCXXXI, Friendseeker, iamtwz, NachtgeistW, R-fzx, Xeonacid, yusancky
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用