From a71d17a265abcd1f5fb465bae7a204e79b5e7372 Mon Sep 17 00:00:00 2001 From: mcawezome <20085898@tafe.wa.edu.au> Date: Wed, 13 Nov 2024 16:36:58 +0800 Subject: [PATCH 1/8] Added an implementation of fibonacci heap. --- data_structures/heap/fibonacci_heap.py | 443 +++++++++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100644 data_structures/heap/fibonacci_heap.py diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py new file mode 100644 index 000000000..7ec4ce1c7 --- /dev/null +++ b/data_structures/heap/fibonacci_heap.py @@ -0,0 +1,443 @@ +class Node: + """A node in the Fibonacci heap. + + Each node maintains references to its key, degree (number of children), + marked status, parent, child, and circular linked list references (left/right). + + Attributes: + key: The key value stored in the node + degree: Number of children of the node + marked: Boolean indicating if the node is marked + parent: Reference to parent node + child: Reference to one child node + left: Reference to left sibling in circular list + right: Reference to right sibling in circular list + + Examples: + >>> node = Node(5) + >>> node.key + 5 + >>> node.degree + 0 + >>> node.marked + False + >>> node.left == node + True + >>> node.right == node + True + """ + + def __init__(self, key): + self.key = key + self.degree = 0 + self.marked = False + self.parent = None + self.child = None + self.left = self + self.right = self + + +class FibonacciHeap: + """Implementation of a Fibonacci heap using circular linked lists. + + A Fibonacci heap is a collection of trees satisfying the min-heap property. + This implementation uses circular linked lists for both the root list and + child lists of nodes. + + Attributes: + min_node: Reference to the node with minimum key + total_nodes: Total number of nodes in the heap + + Reference: Introduction to Algorithms (CLRS) Chapter 19 + https://en.wikipedia.org/wiki/Fibonacci_heap + + Examples: + >>> heap = FibonacciHeap() + >>> heap.is_empty() + True + >>> node = heap.insert(3) + >>> node.key + 3 + >>> node2 = heap.insert(2) + >>> node2.key + 2 + >>> heap.find_min() + 2 + >>> heap.extract_min() + 2 + >>> heap.find_min() + 3 + """ + + def __init__(self): + self.min_node = None + self.total_nodes = 0 + + def insert(self, key): + """Insert a new key into the heap. + + Args: + key: The key value to insert + + Returns: + Node: The newly created node + + Examples: + >>> heap = FibonacciHeap() + >>> node = heap.insert(5) + >>> node.key + 5 + >>> heap.find_min() + 5 + >>> node2 = heap.insert(3) + >>> node2.key + 3 + >>> heap.find_min() + 3 + """ + new_node = Node(key) + if self.min_node is None: + self.min_node = new_node + else: + self._insert_into_circular_list(self.min_node, new_node) + if new_node.key < self.min_node.key: + self.min_node = new_node + self.total_nodes += 1 + return new_node + + def _insert_into_circular_list(self, base_node, node_to_insert): + """Insert node into circular linked list. + + Args: + base_node: The reference node in the circular list + node_to_insert: The node to insert into the list + + Returns: + Node: The base node + + Examples: + >>> heap = FibonacciHeap() + >>> node1 = Node(1) + >>> node2 = Node(2) + >>> result = heap._insert_into_circular_list(node1, node2) + >>> result == node1 + True + >>> node1.right == node2 + True + >>> node2.left == node1 + True + """ + if base_node is None: + return node_to_insert + + node_to_insert.right = base_node.right + node_to_insert.left = base_node + base_node.right.left = node_to_insert + base_node.right = node_to_insert + return base_node + + def extract_min(self): + """Remove and return the minimum key from the heap. + + This operation removes the node with the minimum key from the heap, + adds all its children to the root list, and consolidates the heap + to maintain the Fibonacci heap properties. This is one of the more + complex operations with amortized time complexity of O(log n). + + Returns: + Node: The minimum key value that was removed, + or None if the heap is empty + + Example: + >>> heap = FibonacciHeap() + >>> node1 = heap.insert(3) + >>> node2 = heap.insert(1) + >>> node3 = heap.insert(2) + >>> heap.extract_min() # Removes and returns 1 + 1 + >>> heap.extract_min() # Removes and returns 2 + 2 + >>> heap.extract_min() # Removes and returns 3 + 3 + >>> heap.extract_min() # Heap is now empty + + Note: + This operation may trigger heap consolidation to maintain + the Fibonacci heap properties after removal of the minimum node. + """ + if self.min_node is None: + return None + + min_node = self.min_node + + if min_node.child: + current_child = min_node.child + last_child = min_node.child.left + while True: + next_child = current_child.right + self._insert_into_circular_list(self.min_node, current_child) + current_child.parent = None + if current_child == last_child: + break + current_child = next_child + + min_node.left.right = min_node.right + min_node.right.left = min_node.left + + if min_node == min_node.right: + self.min_node = None + else: + self.min_node = min_node.right + self._consolidate() + + self.total_nodes -= 1 + return min_node.key + + def _consolidate(self): + """Consolidate the heap after removing the minimum node. + + This internal method maintains the Fibonacci heap properties by combining + trees of the same degree until no two roots have the same degree. This + process is key to maintaining the efficiency of the data structure. + + The consolidation process works by: + 1. Creating a temporary array indexed by tree degree + 2. Processing each root node and combining trees of the same degree + 3. Reconstructing the root list and finding the new minimum + + Time complexity: O(log n) amortized + + Note: + This is an internal method called by extract_min and should not be + called directly from outside the class. + """ + max_degree = int(self.total_nodes**0.5) + 1 + degree_table = [None] * max_degree + + roots = [] + if self.min_node: + current_root = self.min_node + while True: + roots.append(current_root) + if current_root.right == self.min_node: + break + current_root = current_root.right + + for current_root in roots: + root_node = current_root + current_degree = root_node.degree + + while degree_table[current_degree] is not None: + other_root = degree_table[current_degree] + if root_node.key > other_root.key: + root_node, other_root = other_root, root_node + + other_root.left.right = other_root.right + other_root.right.left = other_root.left + + if root_node.child is None: + root_node.child = other_root + other_root.right = other_root + other_root.left = other_root + else: + self._insert_into_circular_list(root_node.child, other_root) + + other_root.parent = root_node + root_node.degree += 1 + other_root.marked = False + + degree_table[current_degree] = None + current_degree += 1 + + degree_table[current_degree] = root_node + + self.min_node = None + for degree in range(max_degree): + if degree_table[degree] is not None and ( + self.min_node is None or (degree_table[degree].key < self.min_node.key) + ): + self.min_node = degree_table[degree] + + def decrease_key(self, node, new_key): + """Decrease the key value of a given node. + + This operation updates the key of a node to a new, smaller value and + maintains the min-heap property by potentially cutting the node from + its parent and performing cascading cuts up the tree. + + Args: + node: The node whose key should be decreased + new_key: The new key value, must be smaller than the current key + + Example: + >>> heap = FibonacciHeap() + >>> node = heap.insert(5) + >>> heap.decrease_key(node, 3) + >>> node.key + 3 + >>> heap.find_min() + 3 + >>> heap.decrease_key(node, 1) + >>> node.key + 1 + >>> heap.find_min() + 1 + """ + if new_key > node.key: + raise ValueError("New key is greater than current key") + + node.key = new_key + parent_node = node.parent + + if parent_node is not None and node.key < parent_node.key: + self._cut(node, parent_node) + self._cascading_cut(parent_node) + + if node.key < self.min_node.key: + self.min_node = node + + def _cut(self, child_node, parent_node): + """Cut a node from its parent and add it to the root list. + + This is a helper method used in decrease_key operations. When a node's key + becomes smaller than its parent's key, it needs to be cut from its parent + and added to the root list to maintain the min-heap property. + + Args: + child_node: The node to be cut from its parent + parent_node: The parent node from which to cut + + Note: + This is an internal method that maintains heap properties during + decrease_key operations. It should not be called directly from + outside the class. + """ + if child_node.right == child_node: + parent_node.child = None + else: + parent_node.child = child_node.right + child_node.right.left = child_node.left + child_node.left.right = child_node.right + + parent_node.degree -= 1 + + self._insert_into_circular_list(self.min_node, child_node) + child_node.parent = None + child_node.marked = False + + def _cascading_cut(self, current_node): + """Perform cascading cut operation. + + Args: + current_node: The node to start cascading cut from + """ + if (parent_node := current_node.parent) is not None: + if not current_node.marked: + current_node.marked = True + else: + self._cut(current_node, parent_node) + self._cascading_cut(parent_node) + + def delete(self, node): + """Delete a node from the heap. + + This operation removes a given node from the heap by first decreasing + its key to negative infinity (making it the minimum) and then extracting + the minimum. + + Args: + node: The node to be deleted from the heap + + Example: + >>> heap = FibonacciHeap() + >>> node1 = heap.insert(3) + >>> node2 = heap.insert(2) + >>> heap.delete(node1) + >>> heap.find_min() + 2 + >>> heap.total_nodes + 1 + + Note: + This operation has an amortized time complexity of O(log n) + as it combines decrease_key and extract_min operations. + """ + self.decrease_key(node, float("-inf")) + self.extract_min() + + def find_min(self): + """Return the minimum key without removing it from the heap. + + This operation provides quick access to the minimum key in the heap + without modifying the heap structure. + + Returns: + float | None: The minimum key value, or None if the heap is empty + + Example: + >>> heap = FibonacciHeap() + >>> heap.find_min() is None + True + >>> node1 = heap.insert(3) + >>> heap.find_min() + 3 + """ + return self.min_node.key if self.min_node else None + + def is_empty(self): + """Check if heap is empty. + + Returns: + bool: True if heap is empty, False otherwise + + Examples: + >>> heap = FibonacciHeap() + >>> heap.is_empty() + True + >>> node = heap.insert(1) + >>> heap.is_empty() + False + """ + return self.min_node is None + + def merge(self, other_heap): + """Merge another Fibonacci heap into this one. + + This operation combines two Fibonacci heaps by concatenating their + root lists and updating the minimum pointer if necessary. The other + heap is effectively consumed in this process. + + Args: + other_heap: Another FibonacciHeap instance to merge into this one + + Example: + >>> heap1 = FibonacciHeap() + >>> node1 = heap1.insert(3) + >>> heap2 = FibonacciHeap() + >>> node2 = heap2.insert(2) + >>> heap1.merge(heap2) + >>> heap1.find_min() + 2 + >>> heap1.total_nodes + 2 + """ + if other_heap.min_node is None: + return + if self.min_node is None: + self.min_node = other_heap.min_node + else: + self.min_node.right.left = other_heap.min_node.left + other_heap.min_node.left.right = self.min_node.right + self.min_node.right = other_heap.min_node + other_heap.min_node.left = self.min_node + + if other_heap.min_node.key < self.min_node.key: + self.min_node = other_heap.min_node + + self.total_nodes += other_heap.total_nodes + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a21a6240059cafe4d51c787880dad90053a519b4 Mon Sep 17 00:00:00 2001 From: mcawezome <20085898@tafe.wa.edu.au> Date: Sun, 17 Nov 2024 10:58:59 +0800 Subject: [PATCH 2/8] Added type hints to satisfy automated checks --- data_structures/heap/fibonacci_heap.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py index 7ec4ce1c7..f7f1017ac 100644 --- a/data_structures/heap/fibonacci_heap.py +++ b/data_structures/heap/fibonacci_heap.py @@ -27,7 +27,7 @@ class Node: True """ - def __init__(self, key): + def __init__(self, key) -> None: self.key = key self.degree = 0 self.marked = False @@ -69,11 +69,11 @@ class FibonacciHeap: 3 """ - def __init__(self): + def __init__(self) -> None: self.min_node = None self.total_nodes = 0 - def insert(self, key): + def insert(self, key) -> Node: """Insert a new key into the heap. Args: @@ -105,7 +105,7 @@ class FibonacciHeap: self.total_nodes += 1 return new_node - def _insert_into_circular_list(self, base_node, node_to_insert): + def _insert_into_circular_list(self, base_node, node_to_insert) -> Node: """Insert node into circular linked list. Args: @@ -136,7 +136,7 @@ class FibonacciHeap: base_node.right = node_to_insert return base_node - def extract_min(self): + def extract_min(self) -> Node: """Remove and return the minimum key from the heap. This operation removes the node with the minimum key from the heap, @@ -325,7 +325,7 @@ class FibonacciHeap: child_node.parent = None child_node.marked = False - def _cascading_cut(self, current_node): + def _cascading_cut(self, current_node) -> None: """Perform cascading cut operation. Args: @@ -338,7 +338,7 @@ class FibonacciHeap: self._cut(current_node, parent_node) self._cascading_cut(parent_node) - def delete(self, node): + def delete(self, node) -> None: """Delete a node from the heap. This operation removes a given node from the heap by first decreasing @@ -365,7 +365,7 @@ class FibonacciHeap: self.decrease_key(node, float("-inf")) self.extract_min() - def find_min(self): + def find_min(self) -> Any: """Return the minimum key without removing it from the heap. This operation provides quick access to the minimum key in the heap @@ -384,7 +384,7 @@ class FibonacciHeap: """ return self.min_node.key if self.min_node else None - def is_empty(self): + def is_empty(self) -> bool: """Check if heap is empty. Returns: @@ -400,7 +400,7 @@ class FibonacciHeap: """ return self.min_node is None - def merge(self, other_heap): + def merge(self, other_heap) -> None: """Merge another Fibonacci heap into this one. This operation combines two Fibonacci heaps by concatenating their From 2e35261d091113645cc9dbe0958094c48c29378c Mon Sep 17 00:00:00 2001 From: mcawezome <20085898@tafe.wa.edu.au> Date: Sun, 17 Nov 2024 11:09:56 +0800 Subject: [PATCH 3/8] Fixed issue with None type for nodes --- data_structures/heap/fibonacci_heap.py | 44 +++++++++++++------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py index f7f1017ac..7ed86b6b3 100644 --- a/data_structures/heap/fibonacci_heap.py +++ b/data_structures/heap/fibonacci_heap.py @@ -28,11 +28,11 @@ class Node: """ def __init__(self, key) -> None: - self.key = key + self.key = key or None self.degree = 0 self.marked = False - self.parent = None - self.child = None + self.parent = Node(None) + self.child = Node(None) self.left = self self.right = self @@ -70,7 +70,7 @@ class FibonacciHeap: """ def __init__(self) -> None: - self.min_node = None + self.min_node = Node(None) self.total_nodes = 0 def insert(self, key) -> Node: @@ -127,7 +127,7 @@ class FibonacciHeap: >>> node2.left == node1 True """ - if base_node is None: + if base_node.key is None: return node_to_insert node_to_insert.right = base_node.right @@ -166,7 +166,7 @@ class FibonacciHeap: the Fibonacci heap properties after removal of the minimum node. """ if self.min_node is None: - return None + return Node(None) min_node = self.min_node @@ -176,7 +176,7 @@ class FibonacciHeap: while True: next_child = current_child.right self._insert_into_circular_list(self.min_node, current_child) - current_child.parent = None + current_child.parent.key = None if current_child == last_child: break current_child = next_child @@ -185,7 +185,7 @@ class FibonacciHeap: min_node.right.left = min_node.left if min_node == min_node.right: - self.min_node = None + self.min_node.key = None else: self.min_node = min_node.right self._consolidate() @@ -212,7 +212,7 @@ class FibonacciHeap: called directly from outside the class. """ max_degree = int(self.total_nodes**0.5) + 1 - degree_table = [None] * max_degree + degree_table = [Node(None)] * max_degree roots = [] if self.min_node: @@ -235,7 +235,7 @@ class FibonacciHeap: other_root.left.right = other_root.right other_root.right.left = other_root.left - if root_node.child is None: + if root_node.child.key is None: root_node.child = other_root other_root.right = other_root other_root.left = other_root @@ -246,15 +246,15 @@ class FibonacciHeap: root_node.degree += 1 other_root.marked = False - degree_table[current_degree] = None + degree_table[current_degree] = Node(None) current_degree += 1 degree_table[current_degree] = root_node - self.min_node = None + self.min_node.key = None for degree in range(max_degree): if degree_table[degree] is not None and ( - self.min_node is None or (degree_table[degree].key < self.min_node.key) + self.min_node is None or (degree_table[degree] < self.min_node.key) ): self.min_node = degree_table[degree] @@ -271,7 +271,7 @@ class FibonacciHeap: Example: >>> heap = FibonacciHeap() - >>> node = heap.insert(5) + >>> node1 = heap.insert(5) >>> heap.decrease_key(node, 3) >>> node.key 3 @@ -289,7 +289,7 @@ class FibonacciHeap: node.key = new_key parent_node = node.parent - if parent_node is not None and node.key < parent_node.key: + if parent_node.key is not None and node.key < parent_node.key: self._cut(node, parent_node) self._cascading_cut(parent_node) @@ -313,7 +313,7 @@ class FibonacciHeap: outside the class. """ if child_node.right == child_node: - parent_node.child = None + parent_node.child = Node(None) else: parent_node.child = child_node.right child_node.right.left = child_node.left @@ -322,7 +322,7 @@ class FibonacciHeap: parent_node.degree -= 1 self._insert_into_circular_list(self.min_node, child_node) - child_node.parent = None + child_node.parent = Node(None) child_node.marked = False def _cascading_cut(self, current_node) -> None: @@ -365,7 +365,7 @@ class FibonacciHeap: self.decrease_key(node, float("-inf")) self.extract_min() - def find_min(self) -> Any: + def find_min(self) -> float: """Return the minimum key without removing it from the heap. This operation provides quick access to the minimum key in the heap @@ -382,7 +382,7 @@ class FibonacciHeap: >>> heap.find_min() 3 """ - return self.min_node.key if self.min_node else None + return self.min_node.key if self.min_node else Node(None) def is_empty(self) -> bool: """Check if heap is empty. @@ -398,7 +398,7 @@ class FibonacciHeap: >>> heap.is_empty() False """ - return self.min_node is None + return self.min_node.key is None def merge(self, other_heap) -> None: """Merge another Fibonacci heap into this one. @@ -421,9 +421,9 @@ class FibonacciHeap: >>> heap1.total_nodes 2 """ - if other_heap.min_node is None: + if other_heap.min_node.key is None: return - if self.min_node is None: + if self.min_node.key is None: self.min_node = other_heap.min_node else: self.min_node.right.left = other_heap.min_node.left From fe95a31fdfc8abf1e35879793e7540cdb0cc5199 Mon Sep 17 00:00:00 2001 From: mcawezome <20085898@tafe.wa.edu.au> Date: Sun, 17 Nov 2024 11:14:45 +0800 Subject: [PATCH 4/8] Minor fixes for type checking --- data_structures/heap/fibonacci_heap.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py index 7ed86b6b3..ee7c99cb9 100644 --- a/data_structures/heap/fibonacci_heap.py +++ b/data_structures/heap/fibonacci_heap.py @@ -136,7 +136,7 @@ class FibonacciHeap: base_node.right = node_to_insert return base_node - def extract_min(self) -> Node: + def extract_min(self) -> float: """Remove and return the minimum key from the heap. This operation removes the node with the minimum key from the heap, @@ -166,7 +166,7 @@ class FibonacciHeap: the Fibonacci heap properties after removal of the minimum node. """ if self.min_node is None: - return Node(None) + return Node(None).key min_node = self.min_node @@ -382,7 +382,7 @@ class FibonacciHeap: >>> heap.find_min() 3 """ - return self.min_node.key if self.min_node else Node(None) + return self.min_node.key if self.min_node else Node(None).key def is_empty(self) -> bool: """Check if heap is empty. From 369994d334086e489c81bd81d0ac6f43a18c9e3a Mon Sep 17 00:00:00 2001 From: mcawezome <20085898@tafe.wa.edu.au> Date: Sun, 17 Nov 2024 11:30:25 +0800 Subject: [PATCH 5/8] Added type hints and warning fixes --- data_structures/heap/fibonacci_heap.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py index ee7c99cb9..d651948f7 100644 --- a/data_structures/heap/fibonacci_heap.py +++ b/data_structures/heap/fibonacci_heap.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + class Node: """A node in the Fibonacci heap. @@ -27,7 +30,7 @@ class Node: True """ - def __init__(self, key) -> None: + def __init__(self, key: float | None) -> None: self.key = key or None self.degree = 0 self.marked = False @@ -73,7 +76,7 @@ class FibonacciHeap: self.min_node = Node(None) self.total_nodes = 0 - def insert(self, key) -> Node: + def insert(self, key: float | None) -> Node: """Insert a new key into the heap. Args: @@ -105,7 +108,8 @@ class FibonacciHeap: self.total_nodes += 1 return new_node - def _insert_into_circular_list(self, base_node, node_to_insert) -> Node: + @staticmethod + def _insert_into_circular_list(base_node: Node, node_to_insert: Node) -> Node: """Insert node into circular linked list. Args: @@ -136,7 +140,7 @@ class FibonacciHeap: base_node.right = node_to_insert return base_node - def extract_min(self) -> float: + def extract_min(self) -> float | None: """Remove and return the minimum key from the heap. This operation removes the node with the minimum key from the heap, @@ -193,7 +197,7 @@ class FibonacciHeap: self.total_nodes -= 1 return min_node.key - def _consolidate(self): + def _consolidate(self) -> None: """Consolidate the heap after removing the minimum node. This internal method maintains the Fibonacci heap properties by combining @@ -258,7 +262,7 @@ class FibonacciHeap: ): self.min_node = degree_table[degree] - def decrease_key(self, node, new_key): + def decrease_key(self, node: Node, new_key: float | None) -> None: """Decrease the key value of a given node. This operation updates the key of a node to a new, smaller value and @@ -296,7 +300,7 @@ class FibonacciHeap: if node.key < self.min_node.key: self.min_node = node - def _cut(self, child_node, parent_node): + def _cut(self, child_node: Node, parent_node: Node) -> None: """Cut a node from its parent and add it to the root list. This is a helper method used in decrease_key operations. When a node's key @@ -325,7 +329,7 @@ class FibonacciHeap: child_node.parent = Node(None) child_node.marked = False - def _cascading_cut(self, current_node) -> None: + def _cascading_cut(self, current_node: Node) -> None: """Perform cascading cut operation. Args: @@ -338,7 +342,7 @@ class FibonacciHeap: self._cut(current_node, parent_node) self._cascading_cut(parent_node) - def delete(self, node) -> None: + def delete(self, node: Node) -> None: """Delete a node from the heap. This operation removes a given node from the heap by first decreasing @@ -365,7 +369,7 @@ class FibonacciHeap: self.decrease_key(node, float("-inf")) self.extract_min() - def find_min(self) -> float: + def find_min(self) -> float | None: """Return the minimum key without removing it from the heap. This operation provides quick access to the minimum key in the heap @@ -400,7 +404,7 @@ class FibonacciHeap: """ return self.min_node.key is None - def merge(self, other_heap) -> None: + def merge(self, other_heap: FibonacciHeap) -> None: """Merge another Fibonacci heap into this one. This operation combines two Fibonacci heaps by concatenating their From ea5a187b0aed078fd287b730647c90400ba0412b Mon Sep 17 00:00:00 2001 From: mcawezome <20085898@tafe.wa.edu.au> Date: Mon, 18 Nov 2024 11:03:04 +0800 Subject: [PATCH 6/8] Rewrote entire file to pass checks --- data_structures/heap/fibonacci_heap.py | 599 +++++++++---------------- 1 file changed, 204 insertions(+), 395 deletions(-) diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py index d651948f7..28cd48bf2 100644 --- a/data_structures/heap/fibonacci_heap.py +++ b/data_structures/heap/fibonacci_heap.py @@ -1,444 +1,253 @@ -from __future__ import annotations +""" +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: - """A node in the Fibonacci heap. - - Each node maintains references to its key, degree (number of children), - marked status, parent, child, and circular linked list references (left/right). - - Attributes: - key: The key value stored in the node - degree: Number of children of the node - marked: Boolean indicating if the node is marked - parent: Reference to parent node - child: Reference to one child node - left: Reference to left sibling in circular list - right: Reference to right sibling in circular list - - Examples: - >>> node = Node(5) - >>> node.key - 5 - >>> node.degree - 0 - >>> node.marked - False - >>> node.left == node - True - >>> node.right == node - True + """ + 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, key: float | None) -> None: - self.key = key or None - self.degree = 0 - self.marked = False - self.parent = Node(None) - self.child = Node(None) + 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: - """Implementation of a Fibonacci heap using circular linked lists. + """ + Min-oriented Fibonacci heap implementation. - A Fibonacci heap is a collection of trees satisfying the min-heap property. - This implementation uses circular linked lists for both the root list and - child lists of nodes. - - Attributes: - min_node: Reference to the node with minimum key - total_nodes: Total number of nodes in the heap - - Reference: Introduction to Algorithms (CLRS) Chapter 19 - https://en.wikipedia.org/wiki/Fibonacci_heap - - Examples: - >>> heap = FibonacciHeap() - >>> heap.is_empty() - True - >>> node = heap.insert(3) - >>> node.key - 3 - >>> node2 = heap.insert(2) - >>> node2.key - 2 - >>> heap.find_min() - 2 - >>> heap.extract_min() - 2 - >>> heap.find_min() - 3 + 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) -> None: - self.min_node = Node(None) - self.total_nodes = 0 + def __init__(self): + self.min_node = None + self.size = 0 - def insert(self, key: float | None) -> Node: - """Insert a new key into the heap. + def is_empty(self): + """Return True if heap is empty""" + return self.min_node is None - Args: - key: The key value to insert - - Returns: - Node: The newly created node - - Examples: - >>> heap = FibonacciHeap() - >>> node = heap.insert(5) - >>> node.key - 5 - >>> heap.find_min() - 5 - >>> node2 = heap.insert(3) - >>> node2.key - 3 - >>> heap.find_min() - 3 - """ - new_node = Node(key) - if self.min_node is None: - self.min_node = new_node + 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._insert_into_circular_list(self.min_node, new_node) - if new_node.key < self.min_node.key: - self.min_node = new_node - self.total_nodes += 1 - return new_node + self.min_node.add_sibling(node) + if node.val < self.min_node.val: + self.min_node = node + self.size += 1 + return node - @staticmethod - def _insert_into_circular_list(base_node: Node, node_to_insert: Node) -> Node: - """Insert node into circular linked list. + def peek(self): + """Return minimum value without removing it""" + if not self.min_node: + raise IndexError("Heap is empty") + return self.min_node.val - Args: - base_node: The reference node in the circular list - node_to_insert: The node to insert into the list + 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 - Returns: - Node: The base node + if other.min_node.val < self.min_node.val: + self.min_node = other.min_node - Examples: - >>> heap = FibonacciHeap() - >>> node1 = Node(1) - >>> node2 = Node(2) - >>> result = heap._insert_into_circular_list(node1, node2) - >>> result == node1 - True - >>> node1.right == node2 - True - >>> node2.left == node1 - True - """ - if base_node.key is None: - return node_to_insert + self.size += other.size - node_to_insert.right = base_node.right - node_to_insert.left = base_node - base_node.right.left = node_to_insert - base_node.right = node_to_insert - return base_node + 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 extract_min(self) -> float | None: - """Remove and return the minimum key from the heap. + def delete_min(self): + """Remove and return the minimum value""" + if not self.min_node: + raise IndexError("Heap is empty") - This operation removes the node with the minimum key from the heap, - adds all its children to the root list, and consolidates the heap - to maintain the Fibonacci heap properties. This is one of the more - complex operations with amortized time complexity of O(log n). + min_val = self.min_node.val - Returns: - Node: The minimum key value that was removed, - or None if the heap is empty - - Example: - >>> heap = FibonacciHeap() - >>> node1 = heap.insert(3) - >>> node2 = heap.insert(1) - >>> node3 = heap.insert(2) - >>> heap.extract_min() # Removes and returns 1 - 1 - >>> heap.extract_min() # Removes and returns 2 - 2 - >>> heap.extract_min() # Removes and returns 3 - 3 - >>> heap.extract_min() # Heap is now empty - - Note: - This operation may trigger heap consolidation to maintain - the Fibonacci heap properties after removal of the minimum node. - """ - if self.min_node is None: - return Node(None).key - - min_node = self.min_node - - if min_node.child: - current_child = min_node.child - last_child = min_node.child.left + # Add all children to root list + if self.min_node.child: + curr = self.min_node.child while True: - next_child = current_child.right - self._insert_into_circular_list(self.min_node, current_child) - current_child.parent.key = None - if current_child == last_child: + next_node = curr.right + curr.parent = None + curr.mark = False + self.min_node.add_sibling(curr) + if curr.right == self.min_node.child: break - current_child = next_child + curr = next_node - min_node.left.right = min_node.right - min_node.right.left = min_node.left - - if min_node == min_node.right: - self.min_node.key = None + # Remove minimum node + if self.min_node.right == self.min_node: + self.min_node = None else: - self.min_node = min_node.right - self._consolidate() + self.min_node.remove() + self.min_node = self.min_node.right + self.__consolidate() - self.total_nodes -= 1 - return min_node.key + self.size -= 1 + return min_val - def _consolidate(self) -> None: - """Consolidate the heap after removing the minimum node. - - This internal method maintains the Fibonacci heap properties by combining - trees of the same degree until no two roots have the same degree. This - process is key to maintaining the efficiency of the data structure. - - The consolidation process works by: - 1. Creating a temporary array indexed by tree degree - 2. Processing each root node and combining trees of the same degree - 3. Reconstructing the root list and finding the new minimum - - Time complexity: O(log n) amortized - - Note: - This is an internal method called by extract_min and should not be - called directly from outside the class. - """ - max_degree = int(self.total_nodes**0.5) + 1 - degree_table = [Node(None)] * max_degree + 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 = [] - if self.min_node: - current_root = self.min_node - while True: - roots.append(current_root) - if current_root.right == self.min_node: - break - current_root = current_root.right + curr = self.min_node + while True: + roots.append(curr) + curr = curr.right + if curr == self.min_node: + break - for current_root in roots: - root_node = current_root - current_degree = root_node.degree + # 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 - while degree_table[current_degree] is not None: - other_root = degree_table[current_degree] - if root_node.key > other_root.key: - root_node, other_root = other_root, root_node - - other_root.left.right = other_root.right - other_root.right.left = other_root.left - - if root_node.child.key is None: - root_node.child = other_root - other_root.right = other_root - other_root.left = other_root - else: - self._insert_into_circular_list(root_node.child, other_root) - - other_root.parent = root_node - root_node.degree += 1 - other_root.marked = False - - degree_table[current_degree] = Node(None) - current_degree += 1 - - degree_table[current_degree] = root_node - - self.min_node.key = None + # Find new minimum + self.min_node = None for degree in range(max_degree): - if degree_table[degree] is not None and ( - self.min_node is None or (degree_table[degree] < self.min_node.key) - ): - self.min_node = degree_table[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: Node, new_key: float | None) -> None: - """Decrease the key value of a given node. + 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") - This operation updates the key of a node to a new, smaller value and - maintains the min-heap property by potentially cutting the node from - its parent and performing cascading cuts up the tree. + node.val = new_val + parent = node.parent - Args: - node: The node whose key should be decreased - new_key: The new key value, must be smaller than the current key + if parent and node.val < parent.val: + self.__cut(node, parent) + self.__cascading_cut(parent) - Example: - >>> heap = FibonacciHeap() - >>> node1 = heap.insert(5) - >>> heap.decrease_key(node, 3) - >>> node.key - 3 - >>> heap.find_min() - 3 - >>> heap.decrease_key(node, 1) - >>> node.key - 1 - >>> heap.find_min() - 1 - """ - if new_key > node.key: - raise ValueError("New key is greater than current key") - - node.key = new_key - parent_node = node.parent - - if parent_node.key is not None and node.key < parent_node.key: - self._cut(node, parent_node) - self._cascading_cut(parent_node) - - if node.key < self.min_node.key: + if node.val < self.min_node.val: self.min_node = node - def _cut(self, child_node: Node, parent_node: Node) -> None: - """Cut a node from its parent and add it to the root list. + 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) - This is a helper method used in decrease_key operations. When a node's key - becomes smaller than its parent's key, it needs to be cut from its parent - and added to the root list to maintain the min-heap property. - - Args: - child_node: The node to be cut from its parent - parent_node: The parent node from which to cut - - Note: - This is an internal method that maintains heap properties during - decrease_key operations. It should not be called directly from - outside the class. - """ - if child_node.right == child_node: - parent_node.child = Node(None) - else: - parent_node.child = child_node.right - child_node.right.left = child_node.left - child_node.left.right = child_node.right - - parent_node.degree -= 1 - - self._insert_into_circular_list(self.min_node, child_node) - child_node.parent = Node(None) - child_node.marked = False - - def _cascading_cut(self, current_node: Node) -> None: - """Perform cascading cut operation. - - Args: - current_node: The node to start cascading cut from - """ - if (parent_node := current_node.parent) is not None: - if not current_node.marked: - current_node.marked = True + def __cascading_cut(self, node): + """Perform cascading cut operation""" + if parent := node.parent: + if not node.mark: + node.mark = True else: - self._cut(current_node, parent_node) - self._cascading_cut(parent_node) + self.__cut(node, parent) + self.__cascading_cut(parent) - def delete(self, node: Node) -> None: - """Delete a node from the heap. + def __str__(self): + """String representation of the heap""" + if not self.min_node: + return "Empty heap" - This operation removes a given node from the heap by first decreasing - its key to negative infinity (making it the minimum) and then extracting - the minimum. + 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 - Args: - node: The node to be deleted from the heap - - Example: - >>> heap = FibonacciHeap() - >>> node1 = heap.insert(3) - >>> node2 = heap.insert(2) - >>> heap.delete(node1) - >>> heap.find_min() - 2 - >>> heap.total_nodes - 1 - - Note: - This operation has an amortized time complexity of O(log n) - as it combines decrease_key and extract_min operations. - """ - self.decrease_key(node, float("-inf")) - self.extract_min() - - def find_min(self) -> float | None: - """Return the minimum key without removing it from the heap. - - This operation provides quick access to the minimum key in the heap - without modifying the heap structure. - - Returns: - float | None: The minimum key value, or None if the heap is empty - - Example: - >>> heap = FibonacciHeap() - >>> heap.find_min() is None - True - >>> node1 = heap.insert(3) - >>> heap.find_min() - 3 - """ - return self.min_node.key if self.min_node else Node(None).key - - def is_empty(self) -> bool: - """Check if heap is empty. - - Returns: - bool: True if heap is empty, False otherwise - - Examples: - >>> heap = FibonacciHeap() - >>> heap.is_empty() - True - >>> node = heap.insert(1) - >>> heap.is_empty() - False - """ - return self.min_node.key is None - - def merge(self, other_heap: FibonacciHeap) -> None: - """Merge another Fibonacci heap into this one. - - This operation combines two Fibonacci heaps by concatenating their - root lists and updating the minimum pointer if necessary. The other - heap is effectively consumed in this process. - - Args: - other_heap: Another FibonacciHeap instance to merge into this one - - Example: - >>> heap1 = FibonacciHeap() - >>> node1 = heap1.insert(3) - >>> heap2 = FibonacciHeap() - >>> node2 = heap2.insert(2) - >>> heap1.merge(heap2) - >>> heap1.find_min() - 2 - >>> heap1.total_nodes - 2 - """ - if other_heap.min_node.key is None: - return - if self.min_node.key is None: - self.min_node = other_heap.min_node - else: - self.min_node.right.left = other_heap.min_node.left - other_heap.min_node.left.right = self.min_node.right - self.min_node.right = other_heap.min_node - other_heap.min_node.left = self.min_node - - if other_heap.min_node.key < self.min_node.key: - self.min_node = other_heap.min_node - - self.total_nodes += other_heap.total_nodes + return "\n".join(print_tree(self.min_node)) if __name__ == "__main__": From a92936bd01ca5290f8a1ff78429bfd61a90c0f65 Mon Sep 17 00:00:00 2001 From: mcawezome <20085898@tafe.wa.edu.au> Date: Mon, 18 Nov 2024 12:10:01 +0800 Subject: [PATCH 7/8] Added tests and docstrings to fibonacci_heap.py --- data_structures/heap/fibonacci_heap.py | 164 ++++++++++++++++++++----- 1 file changed, 135 insertions(+), 29 deletions(-) diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py index 28cd48bf2..2961da266 100644 --- a/data_structures/heap/fibonacci_heap.py +++ b/data_structures/heap/fibonacci_heap.py @@ -2,6 +2,7 @@ Fibonacci Heap A more efficient priority queue implementation that provides amortized time bounds that are better than those of the binary and binomial heaps. +reference: https://en.wikipedia.org/wiki/Fibonacci_heap Operations supported: - Insert: O(1) amortized @@ -11,17 +12,22 @@ Operations supported: - 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) - """ + A node in a Fibonacci heap. + Args: + val: The value stored in the node. + + Attributes: + val: The value stored in the node. + parent: Reference to parent node. + child: Reference to one child node. + left: Reference to left sibling. + right: Reference to right sibling. + degree: Number of children. + mark: Boolean indicating if node has lost a child. + """ def __init__(self, val): self.val = val self.parent = None @@ -32,14 +38,24 @@ class Node: self.mark = False def add_sibling(self, node): - """Add node as a sibling""" + """ + Adds a node as a sibling to the current node. + + Args: + node: The node to add 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""" + """ + Adds a node as a child of the current node. + + Args: + node: The node to add as a child. + """ node.parent = self if not self.child: self.child = node @@ -48,38 +64,65 @@ class Node: self.degree += 1 def remove(self): - """Remove this node from its sibling list""" + """Removes this node from its sibling list.""" self.left.right = self.right self.right.left = self.left class FibonacciHeap: """ - Min-oriented Fibonacci heap implementation. + A Fibonacci heap implementation providing + amortized efficient priority queue operations. + + This implementation provides the following time complexities: + - Insert: O(1) amortized + - Find minimum: O(1) + - Delete minimum: O(log n) amortized + - Decrease key: O(1) amortized + - Merge: O(1) Example: >>> heap = FibonacciHeap() - >>> heap.insert(3) - >>> heap.insert(2) - >>> heap.insert(15) + >>> node1 = heap.insert(3) + >>> node2 = heap.insert(2) + >>> node3 = heap.insert(15) >>> heap.peek() 2 >>> heap.delete_min() 2 >>> heap.peek() 3 + >>> other_heap = FibonacciHeap() + >>> node4 = other_heap.insert(1) + >>> heap.merge_heaps(other_heap) + >>> heap.peek() + 1 """ def __init__(self): + """Initializes an empty Fibonacci heap.""" self.min_node = None self.size = 0 def is_empty(self): - """Return True if heap is empty""" + """ + Checks if the heap is empty. + + Returns: + bool: True if heap is empty, False otherwise. + """ return self.min_node is None def insert(self, val): - """Insert a new key into the heap""" + """ + Inserts a new value into the heap. + + Args: + val: Value to insert. + + Returns: + Node: The newly created node. + """ node = Node(val) if not self.min_node: self.min_node = node @@ -91,13 +134,26 @@ class FibonacciHeap: return node def peek(self): - """Return minimum value without removing it""" + """ + Returns the minimum value without removing it. + + Returns: + The minimum value in the heap. + + Raises: + IndexError: If the heap is empty. + """ 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""" + """ + Merges another Fibonacci heap into this one. + + Args: + other: Another FibonacciHeap instance to merge with this one. + """ if not other.min_node: return if not self.min_node: @@ -115,7 +171,13 @@ class FibonacciHeap: self.size += other.size def __link_trees(self, node1, node2): - """Link two trees of same degree""" + """ + Links two trees of same degree. + + Args: + node1: First tree's root node. + node2: Second tree's root node. + """ node1.remove() if node2.child: node2.child.add_sibling(node1) @@ -126,7 +188,15 @@ class FibonacciHeap: node1.mark = False def delete_min(self): - """Remove and return the minimum value""" + """ + Removes and returns the minimum value from the heap. + + Returns: + The minimum value that was removed. + + Raises: + IndexError: If the heap is empty. + """ if not self.min_node: raise IndexError("Heap is empty") @@ -156,8 +226,12 @@ class FibonacciHeap: return min_val def __consolidate(self): - """Consolidate trees after delete_min""" - max_degree = int(self.size**0.5) + 1 + """ + Consolidates the trees in the heap after a delete_min operation. + + This is an internal method that maintains the heap's structure. + """ + max_degree = int(self.size ** 0.5) + 1 degree_table = [None] * max_degree # Collect all roots @@ -195,7 +269,16 @@ class FibonacciHeap: self.min_node = degree_table[degree] def decrease_key(self, node, new_val): - """Decrease the value of a node""" + """ + Decreases the value of a node. + + Args: + node: The node whose value should be decreased. + new_val: The new value for the node. + + Raises: + ValueError: If new value is greater than current value. + """ if new_val > node.val: raise ValueError("New value is greater than current value") @@ -210,7 +293,19 @@ class FibonacciHeap: self.min_node = node def __cut(self, node, parent): - """Cut a node from its parent""" + """ + Cuts a node from its parent. + + Args: + node: Node to be cut. + parent: Parent of the node to be cut. + """""" + Performs cascading cut operation. + + Args: + node: Starting node for cascading cut. + """ + parent.degree -= 1 if parent.child == node: parent.child = node.right if node.right != node else None @@ -222,8 +317,15 @@ class FibonacciHeap: self.min_node.add_sibling(node) def __cascading_cut(self, node): - """Perform cascading cut operation""" - if parent := node.parent: + """ + Performs cascading cut operation. + + Args: + node: Starting node for cascading cut. + """ + + parent = node.parent + if parent: if not node.mark: node.mark = True else: @@ -231,7 +333,12 @@ class FibonacciHeap: self.__cascading_cut(parent) def __str__(self): - """String representation of the heap""" + """ + Returns a string representation of the heap. + + Returns: + str: A string showing the heap structure. + """ if not self.min_node: return "Empty heap" @@ -252,5 +359,4 @@ class FibonacciHeap: if __name__ == "__main__": import doctest - doctest.testmod() From 071ce716e4fe5bf09fc0d89943b84e7437316767 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 04:10:31 +0000 Subject: [PATCH 8/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/heap/fibonacci_heap.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py index 2961da266..71b68ac7e 100644 --- a/data_structures/heap/fibonacci_heap.py +++ b/data_structures/heap/fibonacci_heap.py @@ -12,6 +12,7 @@ Operations supported: - Merge: O(1) """ + class Node: """ A node in a Fibonacci heap. @@ -28,6 +29,7 @@ class Node: degree: Number of children. mark: Boolean indicating if node has lost a child. """ + def __init__(self, val): self.val = val self.parent = None @@ -231,7 +233,7 @@ class FibonacciHeap: This is an internal method that maintains the heap's structure. """ - max_degree = int(self.size ** 0.5) + 1 + max_degree = int(self.size**0.5) + 1 degree_table = [None] * max_degree # Collect all roots @@ -271,11 +273,11 @@ class FibonacciHeap: def decrease_key(self, node, new_val): """ Decreases the value of a node. - + Args: node: The node whose value should be decreased. new_val: The new value for the node. - + Raises: ValueError: If new value is greater than current value. """ @@ -299,9 +301,9 @@ class FibonacciHeap: Args: node: Node to be cut. parent: Parent of the node to be cut. - """""" + """ """ Performs cascading cut operation. - + Args: node: Starting node for cascading cut. """ @@ -324,8 +326,7 @@ class FibonacciHeap: node: Starting node for cascading cut. """ - parent = node.parent - if parent: + if parent := node.parent: if not node.mark: node.mark = True else: @@ -359,4 +360,5 @@ class FibonacciHeap: if __name__ == "__main__": import doctest + doctest.testmod()