配对堆

储存

用 儿子-兄弟表示法 储存,一个节点的所有儿子节点形成一个单向链表。每个节点存储值,第一个儿子的指针,下一个兄弟的指针

任意一个儿子的值都不小于父亲

任何一个满足堆性质的树都是一个合法的配对堆

操作

查询最小值

直接返回堆顶,\(O(1)\)

合并(meld)

若有一个为空则直接返回另一个。否则令两个根节点值较小的为新根节点,较大的插入新根节点的儿子节点链表第一个,时间复杂度 \(O(1)\)

一个节点的儿子链表是按插入时间排序的,即最右边的节点最早成为父节点的儿子,最左边的节点最近成为父节点的儿子

插入

视为一个新的配对堆和原堆合并即可,\(O(1)\)

删除最小值

删去根节点后,将儿子们两两配对并合并(这也是名字的由来),再将合并后的若干堆从右往左(从老到新,否则复杂度会失去保证)依次合并起来

定义函数 merges(x) 表示合并一个节点和它的所有兄弟后新的根节点:

  • 该树为空或没有下一个兄弟则直接返回
  • 否则令 \(y\) 为根节点 \(x\) 的下一个兄弟,令 \(c\)\(y\) 的下一个兄弟
  • 拆散链表,即将 \(x\)\(y\) 指向下一个兄弟的指针置空
  • 配对并合并 \(x\)\(y\),递归合并 \(c\) 和它的兄弟,并将两个新树配对并合并,返回合并结果

定义函数 delete_min 为删除最小值后新的根节点指针:

  • merges 根节点 \(x\) 的第一个儿子,设新根节点为 \(t\)
  • 删除根节点
  • 返回 \(t\)

减小一个元素的值

需要为每个节点添加一个父亲指针,当有左兄弟时指向左兄弟,否则指向真正的父亲

上述操作的同时需要维护父亲指针

减小 \(x\) 的值后,显然它和以它为根的子树任满足堆性质,但 \(x\) 的父亲和它之间不一定满足

因此把以 \(x\) 为根的子树分离出来,把它和剩余的部分合并

定义函数 decrease_key 为减小给定指针指向元素的值后新的根节点,传入所在堆的根指针,操作节点指针和新值:

  • 跟新节点 \(x\) 的值
  • \(x\) 为根则直接返回
  • 否则剖出子树 \(x\)。当 \(x\) 为其真正父亲的第一个儿子时,跟新父亲的儿子指针;否则将它从儿子节点链表中删去。并把 \(x\) 的兄弟指针和父亲指针置空
  • 配对并合并根和 \(x\),返回新的根

时间复杂度

已证明证明 melddelete_min 均为均摊 \(O(\log n)\)decrease-key 操作均摊复杂度下界至少为 \(\Omega(\log\log n)\)

对复杂度上界的估计有:

\(O(1)\) meld

\(O(\log n)\) decrease-key

\(O(2^{2\sqrt{\log\log n}})\) melddecrease-key

前述都是均摊复杂度,不能各取最小,因此不能可持久化

参考

  1. 数据结构——配对堆
  2. oi-wiki 配对堆
posted @ 2024-11-14 07:02  Hstry  阅读(25)  评论(0)    收藏  举报