最近想把优先队列的实现方式都撸一遍,因此出个系列整理。
[算法]优先队列之配对堆
[算法]优先队列之左偏树
各种堆的复杂度分析
方法\堆类型 | 二叉 | 配对 | 斐波那契 | 左偏 | 二顶 |
---|---|---|---|---|---|
查找最小 | 1 | 1 | 1 | 1 | log(n) |
弹出最小 | log(n) | log(n) | log(n) | log(n) | log(n) |
修改(降key) | log(n) | log(n)- | 1 | log(n) | log(n) |
插入 | log(n) | 1 | 1 | log(n) | 1 |
归并 | n | 1 | 1 | log(n) | log(n) |
可持久化 | No | No |
配对堆
配对堆的核心要义在于合并,只要掌握到合并的精髓就能很快的手撸一个配对堆。
- 由于配对堆的插入复杂度只有常数级别,在通常情况下弹出最小值的性能也会由于二叉堆(Java、Python等语言中优先队列的默认实现),所以在做Prim时会有优势。
- 核心API就下文中的两个:
_merge
和_pair_merge
,20行左右还是比较好理解的。 - 若是大范围连续插入,弹出最小值很可能会导致函数栈溢出,因此可以考虑把
_pair_merge
改为迭代的实现方式。 - 若是递增插入,会导致复杂度恶化到O(N)。
参考此篇讲解:https://oi-wiki.org/ds/pairing-heap/ (PS:原文中的代码有较多bug,基本可将其当成伪码处理)
Python实现
定义一个容器类:
class Item:
def __init__(self, priority=0, value=None):
self.priority = priority
self.value = value
self.fa = self.ch = self.bros = None # 父、子指针和兄弟指针 (此处父为左兄或直系父)
def clean(self):
self.fa = self.ch = self.bros = None # 清除父、子指针和兄弟指针
配对堆:
class PriorityQueue: # 配对堆小堆顶 | 空间O(N)
def __init__(self):
self.nodes_num = 0 # 堆内节点元素
self.root = None # 根元素是不会有兄弟节点的
def push(self, item: Item):
if self.root is None:
self.root = item
else:
self.root = self._merge(self.root, item)
self.nodes_num += 1
def poll(self) -> Item: # 核心API
tmp = self.root
if self.size() > 0:
self.root = self._pair_merge(self.root.ch) # 核心:配对合并 O(logN) | 将儿子和其兄弟们进行从左到右的归并
self.nodes_num -= 1
tmp.clean