配对堆Pairing Heap

前言

最近做一道Dijkstra的题目,因为边数实在太大了( 10 9 ),用STL的priority_queue直接超时(事实上还会MLE)。有同学写配对堆的。刚好很久没学习新的数据结构了,就趁着这个机会补一补。

配对堆

配对堆 Pairing Heap 是一种实现简单、均摊复杂度优越的堆数据结构,由Michael Fredman罗伯特·塞奇威克Daniel Sleator罗伯特·塔扬 于1986年发明。 配对堆是一种多叉树,并且可以被认为是一种简化的斐波那契堆
(以上内容来自wikipedia

怎么说呢?
首先,配对堆是一种可并堆,也支持与其他可并堆类似的操作,但是实现和原理较为简单(至少是相对与FIB堆)。

支持操作

配对堆主要支持以下操作:

  • find-min(查找最小值):返回堆顶。
  • merge(合并):比较两个堆顶,将堆顶较大的堆设为另一个的孩子。
  • insert(插入):创建一个只有一个元素的堆,并合并至原堆中。
  • decrease-key(减小元素)(可选):将以该节点为根的子树移除,减小其权值,并合并回去。
  • delete-min(删除最小值):删除根并将其子树合并至一起。这里有各种不同的方式。

接下来我们一个一个详细地讲解这些操作。

说明

  • 我们讲解时用小根堆作为例子(但示例图为大根堆,见谅)
  • 对于每个节点 i ,我们用 S o n i 表示它的儿子节点的集合, F a i 表示它的父亲节点。
  • 一个堆的根我们设为 r o o t

1 FIND-MIN

简单地返回该堆堆顶即可。

2 MERGE

首先合并空堆将返回另一个堆,否则我们返回新堆的根。
假设我们要把 H e a p 1 H e a p 2 这两个堆合并起来。
那么我们需要比较一下 r o o t H e a p 1 r o o t H e a p 2 的权值,不妨设 r o o t H e a p 1 的权值大于 r o o t H e a p 2 的权值。这样我们只需要把 F a r o o t H e a p 1 设为 r o o t H e a p 2 ,并把 r o o t H e a p 1 加入到 S o n r o o t H e a p 2 中即可。
然后返回 r o o t H e a p 2 即可。
P1-MERGE

3 INSERT

假设我们要向堆 H e a p 中加入一个权值为 V a l 的节点。
其实很简单,我们只要新建一个只含有这个节点的堆 H e a p 1 ,然后把它和 H e a p 合并起来就可以了,即使用MERGE操作。
P2-INSERT

4 DECREASE-KEY

这是一个堆中非常常用的操作,尤其对于Dijkstra最短路算法极为有用,这样我们可以避免重复加入一个节点带来的内存和时间开销,而直接更改对应节点具有的距离键值。
假设我们要把堆中节点 u 的权值减少 Δ ,注意, Δ 0
首先,若 u 是当前堆的堆顶,我们直接修改权值即可,堆不会有其他任何变化。
否则,我们将以 u 为根的堆(设为 H e a p u )从原堆中提出,更改它的权值,并用MERGE操作把它和原来的堆合并。
当然这里有一个注意点就是,我们把 H e a p u 提出时,并不直接从其父节点 F a u S o n 集合中删除节点 u ,而是仅讲 F a u 设为 N U L L ,表示它是当前堆的根节点。至于对 F a u 的影响,我们暂不考虑,等到后面的操作再去涉及,以达到均衡复杂度的目的。
P3-DECREASE-KEY1
P4-DECREASE-KEY2

5 DELETE-MIN

这是整个配对堆中最为重要也最为奇妙的操作。
删除根节点 r o o t 后,我们可以直接把所有儿子一个一个合并,但是很明显,这样的复杂度是 O ( n ) 的,非常暴力。
我们当然有更好的方法。标准方法是:首先将子堆从左到右、一对一对地合并(这就是它叫这个名字的原因),然后再从右到左合并该堆。这样子复杂度就是 O ( l o g n ) 的。(事实上这应该该是均摊复杂度,然而我并不会证明)
当然,由于前面的DECREASE-KEY操作, r o o t 可能存在一些“假”的子节点,由于我们需要知道这些子节点并将它们合并,那我们只需要在扫描时判断一下 F a s o n 是否为 r o o t 即可。
P5-DELETE-MIN

复杂度分析

  • find-min : O ( 1 )
  • merge : O ( 1 )
  • insert : O ( 1 )
  • delete-min : O ( l o g n )

这已经非常优秀了。
至于为什么没有写decrease-key,这里有来自wikipedia的一段话:

  • 配对堆时间复杂度的分析灵感来源于伸展树。其delete-min操作的时间复杂度为 O ( l o g n ) ,而find-min、merge和insert操作的均摊时间复杂度均为 O ( 1 )

  • 确定配对堆每次进行decrease-key操作的均摊时间复杂度是困难的。最初,基于经验,这个操作的时间复杂度被推测为是 O ( 1 ) ,但Fredman证明了对于某些操作序列,每次decrease-key操作的时间复杂度至少为 Ω ( log log n ) 。在那之后,通过不同的均摊依据,Pettie证明了insert、merge及decrease-key操作的均摊时间复杂度均为 O ( 2 2 log log n ) ,近似于 o ( log n ) 。Elmasry后来介绍了一种配对堆的变体,令其拥有所有斐波那契堆可以实现的操作,且decrease-key操作的均摊时间复杂度为 O ( log log n ) ,但对于原始的数据结构,仍未知准确的 Θ ( log log n ) 运行下限。此外,能否使delete-min在均摊时间复杂度为 o ( log n ) 的同时,令insert操作的均摊时间复杂度为 O ( 1 ) ,目前也仍未得到解决。

  • 尽管这比其他的,例如能实现均摊时间 O ( 1 ) 的decrease-key的斐波那契堆,这样的优先队列算法更差,在实践中配对堆的表现仍然很不错。Stasko和Vitter, Moret和Shapiro,以及Larkin、Sen和Tarjan进行过配对堆和其他堆数据结构的实验。他们得出的结论是,配对堆通常比基于数组的二叉堆和D叉堆的实际操作速度更快,而且在实践中几乎总是比其他基于指针的堆更快,其中包括诸如斐波纳契堆这样的理论上更有效率的数据结构。

好吧实际上我并没有完全看懂

参考资料

CSDN - PhilipsWeng - Pairing_heap(配对堆)
CSDN - ajian005 - 优先队列三大利器——二项堆、斐波那契堆、Pairing 堆
wikipedia - 词条:配对堆

猜你喜欢

转载自blog.csdn.net/HelloHitler/article/details/81304449
今日推荐