一、二项堆(Binomial Heap):理解「合并操作」的优化
二项堆的核心优势是高效合并,类似 “二进制加法”。我们通过「合并两个二项堆」的伪代码和步骤来理解:
核心结构伪代码:
class BinomialTreeNode:
def __init__(self, key):
self.key = key # 节点值
self.degree = 0 # 度数(子节点数量)
self.parent = None # 父节点
self.children = [] # 子节点列表(按度数从小到大排列)
class BinomialHeap:
def __init__(self):
self.trees = [] # 存储二项树(按度数从小到大排序,每个度数最多1棵)
self.min_node = None # 指向最小元素节点
关键操作:合并两个二项堆(类似二进制加法)
def merge_heaps(h1, h2):
# 1. 合并两个堆的树列表(按度数从小到大)
merged = []
i = j = 0
while i < len(h1.trees) and j < len(h2.trees):
t1 = h1.trees[i]
t2 = h2.trees[j]
if t1.degree < t2.degree:
merged.append(t1)
i += 1
else:
merged.append(t2)
j += 1
merged.extend(h1.trees[i:])
merged.extend(h2.trees[j:])
# 2. 合并相同度数的树(类似二进制进位)
result = BinomialHeap()
carry = None # 用于暂存合并后的树
for tree in merged:
if carry is None:
carry = tree
else:
if carry.degree == tree.degree:
# 合并两棵同度数的树(小根作为父节点)
if carry.key > tree.key:
carry, tree = tree, carry # 保证carry是小根
tree.parent = carry
carry.children.append(tree)
carry.degree += 1 # 度数+1
else:
# 度数不同,直接加入结果
result.trees.append(carry)
carry = tree
if carry is not None:
result.trees.append(carry)
# 3. 更新最小节点
result.update_min()
return result
步骤拆解(合并示例):
假设 h1
有一棵树 B2
(度数 2),h2
有一棵树 B2
(度数 2):
- 合并树列表后,得到两个度数为 2 的树。
- 合并这两棵树:较小根节点作为父,另一棵作为子,得到一棵
B3
(度数 3)。 - 最终结果堆只包含
B3
,完成合并。
核心理解:二项堆的合并像 “二进制加法”,同度数树合并后度数 + 1,效率远高于二叉堆的 O (n) 合并。
二、斐波那契堆(Fibonacci Heap):理解「延迟操作」
斐波那契堆的高效源于 “不立即处理冲突,延迟到必要时”。以「插入」和「提取最小」为例:
核心结构伪代码:
class FibonacciNode:
def __init__(self, key):
self.key = key
self.parent = None
self.children = [] # 子节点列表
self.degree = 0 # 子节点数量
self.marked = False # 标记:是否失去过子节点
class FibonacciHeap:
def __init__(self):
self.root_list = [] # 根节点列表(所有树的根)
self.min_node = None # 最小节点指针
self.node_count = 0 # 总节点数
1. 插入操作(O (1),体现延迟思想):
def insert(self, key):
new_node = FibonacciNode(key)
self.root_list.append(new_node) # 直接加入根列表,不合并
# 更新最小节点
if self.min_node is None or new_node.key < self.min_node.key:
self.min_node = new_node
self.node_count += 1
步骤理解:插入时直接把新节点作为一棵新树加入根列表,不处理任何合并,所以是 O (1)。
2. 提取最小节点(触发延迟处理,摊还 O (log n)):
def extract_min(self):
if self.min_node is None:
return None
# 1. 把最小节点的子节点加入根列表(失去父节点)
for child in self.min_node.children:
self.root_list.append(child)
child.parent = None
# 2. 从根列表移除最小节点
self.root_list.remove(self.min_node)
self.node_count -= 1
# 3. 延迟处理:合并相同度数的树(此时才清理结构)
self.consolidate() # 核心:合并根列表中同度数的树
# 4. 重新找最小节点
self.min_node = self.find_min_in_root_list()
return self.min_node.key
步骤理解:只有提取最小节点时,才会合并根列表中同度数的树(类似二项堆的合并),这个操作的代价被 “摊还” 到之前的插入操作上,所以整体摊还复杂度是 O (log n)。
核心理解:斐波那契堆通过 “延迟合并” 把复杂操作推迟,让简单操作(插入、合并)变得极快。
三、配对堆(Pairing Heap):理解「简单高效的合并」
配对堆的核心是极简的结构和两两合并策略,实现简单却高效。
核心结构伪代码:
class PairingNode:
def __init__(self, key):
self.key = key
self.children = [] # 子节点列表(子堆)
class PairingHeap:
def __init__(self, key=None):
self.root = PairingNode(key) if key is not None else None
1. 合并操作(O (1),体现简单性):
def merge(h1, h2):
# 比较两个根,小根作为父节点,大根作为子节点
if h1 is None:
return h2
if h2 is None:
return h1
if h1.root.key > h2.root.key:
h1, h2 = h2, h1 # 保证h1根更小
# 把h2作为h1的子节点
h1.root.children.append(h2.root)
return h1
步骤理解:合并两个配对堆时,只需把根更大的堆作为子节点挂到根更小的堆上,一步完成,所以是 O (1)。
2. 提取最小节点(用 “配对法” 合并子堆):
def extract_min(self):
if self.root is None:
return None
min_key = self.root.key
# 合并所有子节点(核心:配对法)
if self.root.children:
self.root = self.pairwise_merge(self.root.children)
else:
self.root = None
return min_key
def pairwise_merge(children):
# 第一步:两两合并子节点
if len(children) == 0:
return None
if len(children) == 1:
return children[0]
# 两两合并,递归处理剩余
merged = []
for i in range(0, len(children)-1, 2):
merged_child = merge(children[i], children[i+1])
merged.append(merged_child)
# 若有奇数个,最后一个单独处理
if len(children) % 2 == 1:
merged.append(children[-1])
# 第二步:依次合并结果
return pairwise_merge(merged)
步骤理解:提取最小节点(根节点)后,将其子节点两两合并,再递归合并结果,确保合并效率。这种 “配对合并” 策略让摊还复杂度接近 O (log n)。
核心理解:配对堆用最简单的结构(单根 + 子堆列表)和合并策略,在实践中比理论更高效,适合工程实现。
总结:通过核心操作理解高级堆
堆类型 | 最能体现特性的操作 | 操作逻辑核心 | 为什么高效? |
---|---|---|---|
二项堆 | 合并 | 按度数从小到大合并,同度数树合并为更高一度树(类似二进制加法) | 合并复杂度从 O (n) 降为 O (log n) |
斐波那契堆 | 插入 | 直接加入根列表,不合并(延迟处理) | 简单操作 O (1),复杂操作摊还处理 |
配对堆 | 合并 | 小根作为父,大根作为子,提取时两两合并子堆 | 结构极简,合并逻辑简单,常数小 |
对于初学者,不需要死记代码,重点理解:
- 二项堆是 “有序合并的基础”;
- 斐波那契堆是 “延迟优化的极致”;
- 配对堆是 “简单实用的优选”。
它们的设计思想(如延迟操作、结构化合并)对理解更复杂的算法非常有帮助。