diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py index 68ad764be..b1a182b35 100644 --- a/data_structures/heap/fibonacci_heap.py +++ b/data_structures/heap/fibonacci_heap.py @@ -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