Added full implementation of fibonacci heap

This commit is contained in:
mcawezome 2024-11-25 11:57:05 +11:00
parent 144030aed1
commit 502953aa70

View File

@ -1,8 +1,56 @@
"""
Fibonacci Heap Implementation in Python.
This module provides an implementation of a Fibonacci Heap, a data structure
that supports a priority queue with efficient operations.
Referenced from: https://en.wikipedia.org/wiki/Fibonacci_heap
Classes:
- FibonacciHeapNode: Represents a node in the Fibonacci Heap.
- FibonacciHeap: Represents the Fibonacci Heap itself.
Examples:
>>> fh = FibonacciHeap()
>>> n1 = fh.insert(10, "value1")
>>> n2 = fh.insert(2, "value2")
>>> n3 = fh.insert(15, "value3")
>>> fh.find_min().key
2
>>> fh.decrease_key(n3, 1)
>>> fh.find_min().key
1
>>> fh.extract_min().key
1
>>> fh.find_min().key
2
"""
import math
class FibonacciHeapNode:
"""
Represents a node in the Fibonacci Heap.
Attributes:
key (any): The key of the node.
value (any): The value associated with the key.
degree (int): The number of children of this node.
parent (FibonacciHeapNode): The parent of this node.
child (FibonacciHeapNode): The first child of this node.
mark (bool): Whether this node has lost a child since it became a child of another node.
next (FibonacciHeapNode): The next sibling in the circular doubly-linked list.
prev (FibonacciHeapNode): The previous sibling in the circular doubly-linked list.
"""
def __init__(self, key, value=None):
"""
Initializes a new Fibonacci Heap Node.
Args:
key (any): The key of the node.
value (any, optional): The value associated with the key. Defaults to None.
"""
self.key = key
self.value = value
self.degree = 0
@ -13,6 +61,12 @@ class FibonacciHeapNode:
self.prev = self
def add_child(self, node):
"""
Adds a child node to this node.
Args:
node (FibonacciHeapNode): The child node to be added.
"""
if not self.child:
self.child = node
else:
@ -24,6 +78,12 @@ class FibonacciHeapNode:
self.degree += 1
def remove_child(self, node):
"""
Removes a child node from this node.
Args:
node (FibonacciHeapNode): The child node to be removed.
"""
if node.next == node: # Single child
self.child = None
elif self.child == node:
@ -35,14 +95,55 @@ class FibonacciHeapNode:
class FibonacciHeap:
"""
Represents a Fibonacci Heap.
Attributes:
min_node (FibonacciHeapNode): The node with the minimum key.
total_nodes (int): The total number of nodes in the heap.
"""
def __init__(self):
"""
Initializes an empty Fibonacci Heap.
"""
self.min_node = None
self.total_nodes = 0
def is_empty(self):
"""
Checks if the heap is empty.
Returns:
bool: True if the heap is empty, False otherwise.
Examples:
>>> fh = FibonacciHeap()
>>> fh.is_empty()
True
>>> n1 = fh.insert(5)
>>> fh.is_empty()
False
"""
return self.min_node is None
def insert(self, key, value=None):
"""
Inserts a new node into the heap.
Args:
key (any): The key of the new node.
value (any, optional): The value associated with the key. Defaults to None.
Returns:
FibonacciHeapNode: The newly inserted node.
Examples:
>>> fh = FibonacciHeap()
>>> node = fh.insert(5, "value")
>>> node.key
5
"""
node = FibonacciHeapNode(key, value)
self._merge_with_root_list(node)
if not self.min_node or node.key < self.min_node.key:
@ -51,11 +152,114 @@ class FibonacciHeap:
return node
def find_min(self):
"""
Finds the node with the minimum key.
Returns:
FibonacciHeapNode: The node with the minimum key.
Examples:
>>> fh = FibonacciHeap()
>>> n1 = fh.insert(10)
>>> n2 = fh.insert(2)
>>> fh.find_min().key
2
"""
return self.min_node
def extract_min(self):
"""
Removes and returns the node with the minimum key.
Returns:
FibonacciHeapNode: The node with the minimum key.
Examples:
>>> fh = FibonacciHeap()
>>> n1 = fh.insert(10)
>>> n2 = fh.insert(2)
>>> fh.extract_min().key
2
"""
temp_min_node = self.min_node
if temp_min_node:
if temp_min_node.child:
children = list(self._iterate(temp_min_node.child))
for child in children:
self._merge_with_root_list(child)
child.parent = None
self._remove_from_root_list(temp_min_node)
if temp_min_node == temp_min_node.next:
self.min_node = None
else:
self.min_node = temp_min_node.next
self._consolidate()
self.total_nodes -= 1
return temp_min_node
def decrease_key(self, node, new_key):
"""
Decreases the key of a given node.
Args:
node (FibonacciHeapNode): The node to decrease the key for.
new_key (any): The new key value.
Raises:
ValueError: If the new key is greater than the current key.
Examples:
>>> fh = FibonacciHeap()
>>> node = fh.insert(10)
>>> fh.decrease_key(node, 5)
>>> fh.find_min().key
5
"""
if new_key > node.key:
raise ValueError("New key is greater than current key")
node.key = new_key
temp_parent = node.parent
if temp_parent and node.key < temp_parent.key:
self._cut(node, temp_parent)
self._cascading_cut(temp_parent)
if node.key < self.min_node.key:
self.min_node = node
def delete(self, x):
"""
Deletes a given node from the heap.
Args:
x (FibonacciHeapNode): The node to be deleted.
Examples:
>>> fh = FibonacciHeap()
>>> node = fh.insert(10)
>>> fh.delete(node)
>>> fh.is_empty()
True
"""
self.decrease_key(x, -math.inf)
self.extract_min()
def union(self, other_heap):
"""
Merges another Fibonacci Heap into this heap.
Args:
other_heap (FibonacciHeap): The other Fibonacci Heap to be merged.
Examples:
>>> fh1 = FibonacciHeap()
>>> fh2 = FibonacciHeap()
>>> n1 = fh1.insert(10)
>>> n2 = fh2.insert(5)
>>> fh1.union(fh2)
>>> fh1.find_min().key
5
"""
if not other_heap.min_node:
return self
return
if not self.min_node:
self.min_node = other_heap.min_node
else:
@ -64,52 +268,13 @@ class FibonacciHeap:
self.min_node = other_heap.min_node
self.total_nodes += other_heap.total_nodes
def extract_min(self):
z = self.min_node
if z:
if z.child:
children = list(self._iterate(z.child))
for child in children:
self._merge_with_root_list(child)
child.parent = None
self._remove_from_root_list(z)
if z == z.next:
self.min_node = None
else:
self.min_node = z.next
self._consolidate()
self.total_nodes -= 1
return z
def decrease_key(self, x, new_key):
if new_key > x.key:
raise ValueError("New key is greater than current key")
x.key = new_key
y = x.parent
if y and x.key < y.key:
self._cut(x, y)
self._cascading_cut(y)
if x.key < self.min_node.key:
self.min_node = x
def delete(self, x):
self.decrease_key(x, -math.inf)
self.extract_min()
def _cut(self, x, y):
y.remove_child(x)
self._merge_with_root_list(x)
x.mark = False
def _cascading_cut(self, y):
if z := y.parent:
if not y.mark:
y.mark = True
else:
self._cut(y, z)
self._cascading_cut(z)
def _merge_with_root_list(self, node):
"""
Merges a node into the root list.
Args:
node (FibonacciHeapNode): The node to be merged.
"""
if not self.min_node:
self.min_node = node
else:
@ -119,6 +284,12 @@ class FibonacciHeap:
self.min_node.next = node
def _remove_from_root_list(self, node):
"""
Removes a node from the root list.
Args:
node (FibonacciHeapNode): The node to be removed.
"""
if node.next == node:
self.min_node = None
else:
@ -126,20 +297,23 @@ class FibonacciHeap:
node.next.prev = node.prev
def _consolidate(self):
"""
Consolidates the heap by combining trees of the same degree.
"""
array_size = int(math.log(self.total_nodes) * 2) + 1
array = [None] * array_size
nodes = list(self._iterate(self.min_node))
for w in nodes:
x = w
d = x.degree
while array[d]:
y = array[d]
if x.key > y.key:
x, y = y, x
self._link(y, x)
array[d] = None
d += 1
array[d] = x
for node in nodes:
temp_node = node
degree = temp_node.degree
while array[degree]:
array_node = array[degree]
if temp_node.key > array_node.key:
temp_node, array_node = array_node, temp_node
self._link(array_node, temp_node)
array[degree] = None
degree += 1
array[degree] = temp_node
self.min_node = None
for i in range(array_size):
if array[i]:
@ -150,29 +324,58 @@ class FibonacciHeap:
if array[i].key < self.min_node.key:
self.min_node = array[i]
def _link(self, y, x):
self._remove_from_root_list(y)
x.add_child(y)
y.mark = False
def _link(self, node_to_link, node_to_parent):
"""
Links two nodes by making one a child of the other.
Args:
node_to_link (FibonacciHeapNode): The node to be linked as a child.
node_to_parent (FibonacciHeapNode): The node to be the parent.
"""
self._remove_from_root_list(node_to_link)
node_to_parent.add_child(node_to_link)
node_to_link.mark = False
def _cut(self, node_to_cut, parent_node):
"""
Cuts a node from its parent and adds it to the root list.
Args:
node_to_cut (FibonacciHeapNode): The node to be cut.
parent_node (FibonacciHeapNode): The parent node.
"""
parent_node.remove_child(node_to_cut)
self._merge_with_root_list(node_to_cut)
node_to_cut.mark = False
def _cascading_cut(self, node_to_cut):
"""
Performs a cascading cut operation.
Args:
node_to_cut (FibonacciHeapNode): The node to be cut recursively.
"""
temp_parent = node_to_cut.parent
if temp_parent:
if not node_to_cut.mark:
node_to_cut.mark = True
else:
self._cut(node_to_cut, temp_parent)
self._cascading_cut(temp_parent)
def _iterate(self, start):
"""
Iterates through a circular doubly linked list starting at a given node.
Args:
start (FibonacciHeapNode): The starting node.
Yields:
FibonacciHeapNode: The next node in the list.
"""
node = start
while True:
yield node
node = node.next
if node == start:
break
# Example usage
if __name__ == "__main__":
fh = FibonacciHeap()
n1 = fh.insert(10, "value1")
n2 = fh.insert(2, "value2")
n3 = fh.insert(15, "value3")
print("Min:", fh.find_min().key) # Output: 2
fh.decrease_key(n3, 1)
print("Min after decrease key:", fh.find_min().key) # Output: 1
fh.extract_min()
print("Min after extract:", fh.find_min().key) # Output: 2