Python/data_structures/heap/fibonacci_heap.py
2024-11-18 11:03:04 +08:00

257 lines
7.1 KiB
Python

"""
Fibonacci Heap
A more efficient priority queue implementation that provides amortized time bounds
that are better than those of the binary and binomial heaps.
Operations supported:
- Insert: O(1) amortized
- Find minimum: O(1)
- Delete minimum: O(log n) amortized
- Decrease key: O(1) amortized
- Merge: O(1)
"""
class Node:
"""
Node in a Fibonacci heap containing:
- value
- parent, child, and sibling links
- degree (number of children)
- mark (whether the node has lost a child since
becoming a child of its current parent)
"""
def __init__(self, val):
self.val = val
self.parent = None
self.child = None
self.left = self
self.right = self
self.degree = 0
self.mark = False
def add_sibling(self, node):
"""Add node as a sibling"""
node.left = self
node.right = self.right
self.right.left = node
self.right = node
def add_child(self, node):
"""Add node as a child"""
node.parent = self
if not self.child:
self.child = node
else:
self.child.add_sibling(node)
self.degree += 1
def remove(self):
"""Remove this node from its sibling list"""
self.left.right = self.right
self.right.left = self.left
class FibonacciHeap:
"""
Min-oriented Fibonacci heap implementation.
Example:
>>> heap = FibonacciHeap()
>>> heap.insert(3)
>>> heap.insert(2)
>>> heap.insert(15)
>>> heap.peek()
2
>>> heap.delete_min()
2
>>> heap.peek()
3
"""
def __init__(self):
self.min_node = None
self.size = 0
def is_empty(self):
"""Return True if heap is empty"""
return self.min_node is None
def insert(self, val):
"""Insert a new key into the heap"""
node = Node(val)
if not self.min_node:
self.min_node = node
else:
self.min_node.add_sibling(node)
if node.val < self.min_node.val:
self.min_node = node
self.size += 1
return node
def peek(self):
"""Return minimum value without removing it"""
if not self.min_node:
raise IndexError("Heap is empty")
return self.min_node.val
def merge_heaps(self, other):
"""Merge another Fibonacci heap with this one"""
if not other.min_node:
return
if not self.min_node:
self.min_node = other.min_node
else:
# Merge root lists
self.min_node.right.left = other.min_node.left
other.min_node.left.right = self.min_node.right
self.min_node.right = other.min_node
other.min_node.left = self.min_node
if other.min_node.val < self.min_node.val:
self.min_node = other.min_node
self.size += other.size
def __link_trees(self, node1, node2):
"""Link two trees of same degree"""
node1.remove()
if node2.child:
node2.child.add_sibling(node1)
else:
node2.child = node1
node1.parent = node2
node2.degree += 1
node1.mark = False
def delete_min(self):
"""Remove and return the minimum value"""
if not self.min_node:
raise IndexError("Heap is empty")
min_val = self.min_node.val
# Add all children to root list
if self.min_node.child:
curr = self.min_node.child
while True:
next_node = curr.right
curr.parent = None
curr.mark = False
self.min_node.add_sibling(curr)
if curr.right == self.min_node.child:
break
curr = next_node
# Remove minimum node
if self.min_node.right == self.min_node:
self.min_node = None
else:
self.min_node.remove()
self.min_node = self.min_node.right
self.__consolidate()
self.size -= 1
return min_val
def __consolidate(self):
"""Consolidate trees after delete_min"""
max_degree = int(self.size**0.5) + 1
degree_table = [None] * max_degree
# Collect all roots
roots = []
curr = self.min_node
while True:
roots.append(curr)
curr = curr.right
if curr == self.min_node:
break
# Consolidate trees
for root in roots:
degree = root.degree
while degree_table[degree]:
other = degree_table[degree]
if root.val > other.val:
root, other = other, root
self.__link_trees(other, root)
degree_table[degree] = None
degree += 1
degree_table[degree] = root
# Find new minimum
self.min_node = None
for degree in range(max_degree):
if degree_table[degree]:
if not self.min_node:
self.min_node = degree_table[degree]
self.min_node.left = self.min_node
self.min_node.right = self.min_node
else:
self.min_node.add_sibling(degree_table[degree])
if degree_table[degree].val < self.min_node.val:
self.min_node = degree_table[degree]
def decrease_key(self, node, new_val):
"""Decrease the value of a node"""
if new_val > node.val:
raise ValueError("New value is greater than current value")
node.val = new_val
parent = node.parent
if parent and node.val < parent.val:
self.__cut(node, parent)
self.__cascading_cut(parent)
if node.val < self.min_node.val:
self.min_node = node
def __cut(self, node, parent):
"""Cut a node from its parent"""
parent.degree -= 1
if parent.child == node:
parent.child = node.right if node.right != node else None
node.remove()
node.left = node
node.right = node
node.parent = None
node.mark = False
self.min_node.add_sibling(node)
def __cascading_cut(self, node):
"""Perform cascading cut operation"""
if parent := node.parent:
if not node.mark:
node.mark = True
else:
self.__cut(node, parent)
self.__cascading_cut(parent)
def __str__(self):
"""String representation of the heap"""
if not self.min_node:
return "Empty heap"
def print_tree(node, level=0):
result = []
curr = node
while True:
result.append("-" * level + str(curr.val))
if curr.child:
result.extend(print_tree(curr.child, level + 1))
curr = curr.right
if curr == node:
break
return result
return "\n".join(print_tree(self.min_node))
if __name__ == "__main__":
import doctest
doctest.testmod()