前言

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

配对堆

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

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

支持操作

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

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

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

说明

  • 我们讲解时用小根堆作为例子(但示例图为大根堆,见谅)
  • 对于每个节点iii,我们用Soni" role="presentation">SoniSoniSon_i表示它的儿子节点的集合,FaiFaiFa_i表示它的父亲节点。
  • 一个堆的根我们设为rootrootroot

1 FIND-MIN

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

2 MERGE

首先合并空堆将返回另一个堆,否则我们返回新堆的根。
假设我们要把Heap1Heap1Heap1和Heap2Heap2Heap2这两个堆合并起来。
那么我们需要比较一下rootHeap1rootHeap1root_{Heap1}和rootHeap2rootHeap2root_{Heap2}的权值,不妨设rootHeap1rootHeap1root_{Heap1}的权值大于rootHeap2rootHeap2root_{Heap2}的权值。这样我们只需要把FarootHeap1FarootHeap1Fa_{root_{Heap1}}设为rootHeap2rootHeap2root_{Heap2},并把rootHeap1rootHeap1root_{Heap1}加入到SonrootHeap2SonrootHeap2Son_{root_{Heap2}}中即可。
然后返回rootHeap2rootHeap2root_{Heap2}即可。

3 INSERT

假设我们要向堆HeapHeapHeap中加入一个权值为ValValVal的节点。
其实很简单,我们只要新建一个只含有这个节点的堆Heap1Heap1Heap1,然后把它和HeapHeapHeap合并起来就可以了,即使用MERGE操作。

4 DECREASE-KEY

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

5 DELETE-MIN

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

复杂度分析

  • find-min : O(1)O(1)O(1)
  • merge : O(1)O(1)O(1)
  • insert : O(1)O(1)O(1)
  • delete-min : O(logn)O(logn)O(log \, n)

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

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

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

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

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

参考资料

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

配对堆Pairing Heap相关推荐

  1. Jemalloc 深入分析 之 配对堆Pairing Heap

    为了更好的阅读效果,推荐下载pdf文档: 详细文章请参考:<jemalloc 深入分析> https://github.com/everschen/tools/blob/master/DO ...

  2. 配对堆(Pairing Heap)

    配对堆(Pairing Heap)是一个简单实用的min-heap结构(当然也可以做成max-heap).它是一颗多路树(multiway tree),类似于Leftist Heap和Skew Hea ...

  3. 结构之美——优先队列三大结构(三)——Pairing Heap

    转自http://dsqiu.iteye.com/blog/1714961 1.Pairing Heap简介 斐波那契堆主要有两个缺点:编程实现难度较大和实际效率没有理论的那么快(由于它的存储结构和四 ...

  4. 堆(heap)与栈(stack)的区别(一)

    堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收,但它与数据结构中的堆不是一回事,分配方式类似于链表. 栈(stack):由编译器自动分配和释放,存函数的参数值, ...

  5. 【编程】堆(heap)和栈(stack)的区别

    从C/C++的内存分配(与操作系统相关)上来说,堆(heap),栈(stack)属于内存空间的一段区域. 效率: 栈是机器系统提供的数据结构,计算机会在底层对栈提供支持(有专门的寄存器存放栈的地址,压 ...

  6. C++二叉堆binary heap (附完整源码)

    二叉堆binary heap 二叉堆binary heap 算法的完整源码(定义,实现,main函数测试) 二叉堆binary heap 算法的完整源码(定义,实现,main函数测试) #includ ...

  7. 11.JDK8内存模型、本地方法栈、虚拟机栈、栈帧结构(局部变量表、操作数栈、方法出口、虚拟机栈与本地方法栈的关系、寄存器、方法区、堆(Heap)、jvm中的常量池、Metaspace(元空间))

    11.JDK8内存模型 11.1.本地方法栈(Native Method Stacks) 11.2.虚拟机栈(Java Virtual Machine Stacks) 11.3.栈帧结构 11.3.1 ...

  8. 返回局部变量或临时变量的地址_值传递和地址返回两者在堆区(Heap)应用的三种易错点...

    1.指针变量作为参数进行值传递给函数的形参,并在堆区(Heap)进行内存分配和赋值 程序源码: 1 结果: Segmentation fault (core dumped) 分析: 如上图,指针变量p ...

  9. 看动画学算法之:二叉堆Binary Heap

    文章目录 简介 二叉堆的特性 二叉堆的作用 二叉堆的构建 获取二叉堆的最大值 二叉堆的插入 insert操作的时间复杂度 二叉堆的提取Max操作 extractMax的时间复杂度 创建二叉堆 简介 我 ...

最新文章

  1. Linux下查看nginx apache mysql php的编译参数
  2. Map的Value值转换为List集合
  3. Jlink-V9详细制作材料(带串口+SW)----小白的福音
  4. android 使用AIDL实现进程间通讯
  5. C语言 二维数组做函数参数的几种情况
  6. LeetCode 2019 力扣杯全国秋季编程大赛
  7. 易生信九天的转录组分析培训班总结
  8. 【Linux】Another app is currently holding the yum lock; waiting for it to exit...
  9. LeetCode #1349. 参加考试的最大学生数 - 学到了:压缩状态动态规划、位运算、reduce()、str().count()
  10. Akka源码分析-Actor创建
  11. matplotlib.pyplot中API介绍
  12. 如何判断自己的Windows系统是否为盗版系统?
  13. 各种cms getshell技巧
  14. Python3 wxPython库
  15. 入门HTML之表格入门基本属性
  16. 9.9 单片机蜂鸣器
  17. 收到offer不想去,如何优雅拒绝?
  18. Unity Super TileMap Editor使用帮助翻译
  19. Unity3d优化总结2
  20. Kotlin入门:var和val的区别

热门文章

  1. 【JZOJ4787】数格子【矩阵乘法】
  2. java web pdf 下载文件_javaWeb实现文件上传和下载.pdf
  3. 利用rect函数截取原图的图像OPENCV
  4. IIS管理器和文件流
  5. 百度地图在VUE项目中插入点位及点位信息弹框
  6. 采用傅里叶模态法分析闪耀光栅
  7. 如何逃避classin专注学习模式
  8. 正正电商源码--正正广告电商系统开发源码分享
  9. 伺服系统震动27问,全部问到点子上了
  10. PTA 7-33 统计素数并求和