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 01/15] 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 000000000000..7ec4ce1c78e6
--- /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 02/15] 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 7ec4ce1c78e6..f7f1017ac504 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 @@ def insert(self, key):
         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 @@ def _insert_into_circular_list(self, base_node, node_to_insert):
         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 @@ def _cut(self, child_node, parent_node):
         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 @@ def _cascading_cut(self, current_node):
                 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 @@ def delete(self, node):
         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 @@ def find_min(self):
         """
         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 @@ def is_empty(self):
         """
         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 03/15] 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 f7f1017ac504..7ed86b6b3753 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 @@ def _insert_into_circular_list(self, base_node, node_to_insert) -> Node:
             >>> 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 @@ def extract_min(self) -> Node:
             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 @@ def extract_min(self) -> Node:
             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 @@ def extract_min(self) -> Node:
         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 @@ def _consolidate(self):
             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 @@ def _consolidate(self):
                 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 @@ def _consolidate(self):
                 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 @@ def decrease_key(self, node, new_key):
 
         Example:
             >>> heap = FibonacciHeap()
-            >>> node = heap.insert(5)
+            >>> node1 = heap.insert(5)
             >>> heap.decrease_key(node, 3)
             >>> node.key
             3
@@ -289,7 +289,7 @@ def decrease_key(self, node, new_key):
         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 @@ def _cut(self, child_node, parent_node):
             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 @@ def _cut(self, child_node, parent_node):
         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 @@ def delete(self, node) -> None:
         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 @@ def find_min(self) -> Any:
             >>> 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 @@ def is_empty(self) -> bool:
             >>> 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 @@ def merge(self, other_heap) -> None:
             >>> 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 04/15] 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 7ed86b6b3753..ee7c99cb9391 100644
--- a/data_structures/heap/fibonacci_heap.py
+++ b/data_structures/heap/fibonacci_heap.py
@@ -136,7 +136,7 @@ def _insert_into_circular_list(self, base_node, node_to_insert) -> Node:
         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 @@ def extract_min(self) -> Node:
             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 @@ def find_min(self) -> float:
             >>> 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 05/15] 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 ee7c99cb9391..d651948f74fa 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 @@ def __init__(self) -> None:
         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 @@ def insert(self, key) -> Node:
         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 @@ def _insert_into_circular_list(self, base_node, node_to_insert) -> Node:
         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 @@ def extract_min(self) -> float:
         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 @@ def _consolidate(self):
             ):
                 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 @@ def decrease_key(self, node, new_key):
         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 @@ def _cut(self, child_node, parent_node):
         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 @@ def _cascading_cut(self, current_node) -> None:
                 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 @@ def delete(self, node) -> None:
         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 @@ def is_empty(self) -> bool:
         """
         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 06/15] Rewrote entire file to pass checks

---
 data_structures/heap/fibonacci_heap.py | 623 +++++++++----------------
 1 file changed, 216 insertions(+), 407 deletions(-)

diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py
index d651948f74fa..28cd48bf23ad 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.
-
-    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
+    """
+    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) -> None:
-        self.min_node = Node(None)
-        self.total_nodes = 0
-
-    def insert(self, key: float | None) -> Node:
-        """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
-
-    @staticmethod
-    def _insert_into_circular_list(base_node: Node, node_to_insert: Node) -> Node:
-        """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.key 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) -> float | None:
-        """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 Node(None).key
-
-        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.key = None
-                if current_child == last_child:
-                    break
-                current_child = next_child
+    def __init__(self):
+        self.min_node = None
+        self.size = 0
 
-        min_node.left.right = min_node.right
-        min_node.right.left = min_node.left
+    def is_empty(self):
+        """Return True if heap is empty"""
+        return self.min_node is None
 
-        if min_node == min_node.right:
-            self.min_node.key = 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 = min_node.right
-            self._consolidate()
-
-        self.total_nodes -= 1
-        return min_node.key
-
-    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
+            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
 
-        Time complexity: O(log n) amortized
+    def delete_min(self):
+        """Remove and return the minimum value"""
+        if not self.min_node:
+            raise IndexError("Heap is empty")
 
-        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
+        min_val = self.min_node.val
 
-        roots = []
-        if self.min_node:
-            current_root = self.min_node
+        # Add all children to root list
+        if self.min_node.child:
+            curr = self.min_node.child
             while True:
-                roots.append(current_root)
-                if current_root.right == self.min_node:
+                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_root = current_root.right
+                curr = next_node
 
-        for current_root in roots:
-            root_node = current_root
-            current_degree = root_node.degree
+        # 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()
 
-            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
+        self.size -= 1
+        return min_val
 
-                other_root.left.right = other_root.right
-                other_root.right.left = other_root.left
+    def __consolidate(self):
+        """Consolidate trees after delete_min"""
+        max_degree = int(self.size**0.5) + 1
+        degree_table = [None] * max_degree
 
-                if root_node.child.key is None:
-                    root_node.child = other_root
-                    other_root.right = other_root
-                    other_root.left = other_root
+        # 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._insert_into_circular_list(root_node.child, other_root)
+                    self.min_node.add_sibling(degree_table[degree])
+                    if degree_table[degree].val < self.min_node.val:
+                        self.min_node = degree_table[degree]
 
-                other_root.parent = root_node
-                root_node.degree += 1
-                other_root.marked = False
+    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")
 
-                degree_table[current_degree] = Node(None)
-                current_degree += 1
+        node.val = new_val
+        parent = node.parent
 
-            degree_table[current_degree] = root_node
+        if parent and node.val < parent.val:
+            self.__cut(node, parent)
+            self.__cascading_cut(parent)
 
-        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] < self.min_node.key)
-            ):
-                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.
-
-        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()
-            >>> 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.
-
-        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 __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(current_node, parent_node)
-                self._cascading_cut(parent_node)
-
-    def delete(self, node: Node) -> None:
-        """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) -> 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
+                self.__cut(node, parent)
+                self.__cascading_cut(parent)
+
+    def __str__(self):
+        """String representation of the heap"""
+        if not self.min_node:
+            return "Empty heap"
 
-            if other_heap.min_node.key < self.min_node.key:
-                self.min_node = other_heap.min_node
+        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
 
-        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 07/15] 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 28cd48bf23ad..2961da26608c 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 @@
 - 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 @@ def __init__(self, val):
         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 @@ def add_child(self, 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 @@ def insert(self, val):
         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 @@ def merge_heaps(self, other):
         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 @@ def __link_trees(self, node1, node2):
         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 @@ def delete_min(self):
         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 @@ def __consolidate(self):
                         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 @@ def decrease_key(self, node, new_val):
             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 @@ def __cut(self, node, parent):
         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 @@ def __cascading_cut(self, node):
                 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 @@ def print_tree(node, level=0):
 
 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 08/15] [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 2961da26608c..71b68ac7e8a5 100644
--- a/data_structures/heap/fibonacci_heap.py
+++ b/data_structures/heap/fibonacci_heap.py
@@ -12,6 +12,7 @@
 - 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 @@ def __consolidate(self):
 
         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 @@ def __consolidate(self):
     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 @@ def __cut(self, node, 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.
         """
@@ -324,8 +326,7 @@ def __cascading_cut(self, node):
             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 @@ def print_tree(node, level=0):
 
 if __name__ == "__main__":
     import doctest
+
     doctest.testmod()

From bd943b086a9a470ef853d50bbea1fa207e5cb4ec Mon Sep 17 00:00:00 2001
From: mcawezome <20085898@tafe.wa.edu.au>
Date: Mon, 18 Nov 2024 12:12:17 +0800
Subject: [PATCH 09/15] Added tests and docstrings to fibonacci_heap.py

---
 data_structures/heap/fibonacci_heap.py | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py
index 71b68ac7e8a5..2961da26608c 100644
--- a/data_structures/heap/fibonacci_heap.py
+++ b/data_structures/heap/fibonacci_heap.py
@@ -12,7 +12,6 @@
 - Merge: O(1)
 """
 
-
 class Node:
     """
     A node in a Fibonacci heap.
@@ -29,7 +28,6 @@ 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
@@ -233,7 +231,7 @@ def __consolidate(self):
 
         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
@@ -273,11 +271,11 @@ def __consolidate(self):
     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.
         """
@@ -301,9 +299,9 @@ def __cut(self, node, 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.
         """
@@ -326,7 +324,8 @@ def __cascading_cut(self, node):
             node: Starting node for cascading cut.
         """
 
-        if parent := node.parent:
+        parent = node.parent
+        if parent:
             if not node.mark:
                 node.mark = True
             else:
@@ -360,5 +359,4 @@ def print_tree(node, level=0):
 
 if __name__ == "__main__":
     import doctest
-
     doctest.testmod()

From a1291fd5cacbba87cd8e5a9b38cce5e919334cc2 Mon Sep 17 00:00:00 2001
From: mcawezome <20085898@tafe.wa.edu.au>
Date: Mon, 18 Nov 2024 12:21:36 +0800
Subject: [PATCH 10/15] Added tests and docstrings to fibonacci_heap.py

---
 data_structures/heap/fibonacci_heap.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py
index 2961da26608c..13e58a0d8305 100644
--- a/data_structures/heap/fibonacci_heap.py
+++ b/data_structures/heap/fibonacci_heap.py
@@ -294,7 +294,7 @@ def decrease_key(self, node, new_val):
 
     def __cut(self, node, parent):
         """
-        Cuts a node from its parent.
+        Cuts a node from its parent
 
         Args:
             node: Node to be cut.

From c4516d1dfc757210808c23c545896595ca561861 Mon Sep 17 00:00:00 2001
From: mcawezome <20085898@tafe.wa.edu.au>
Date: Mon, 18 Nov 2024 12:26:21 +0800
Subject: [PATCH 11/15] Added tests and docstrings to fibonacci_heap.py

---
 data_structures/heap/fibonacci_heap.py | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py
index 13e58a0d8305..79ed58ec5edd 100644
--- a/data_structures/heap/fibonacci_heap.py
+++ b/data_structures/heap/fibonacci_heap.py
@@ -271,11 +271,11 @@ def __consolidate(self):
     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,11 +299,6 @@ def __cut(self, node, 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

From 144030aed1079d9a8c1bb06db36aac3459ac15f1 Mon Sep 17 00:00:00 2001
From: mcawezome <20085898@tafe.wa.edu.au>
Date: Thu, 21 Nov 2024 22:10:25 +1100
Subject: [PATCH 12/15] Retried commit with base fib heap implementation2

---
 data_structures/heap/fibonacci_heap.py | 455 ++++++++-----------------
 1 file changed, 138 insertions(+), 317 deletions(-)

diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py
index 79ed58ec5edd..68ad764bee0d 100644
--- a/data_structures/heap/fibonacci_heap.py
+++ b/data_structures/heap/fibonacci_heap.py
@@ -1,357 +1,178 @@
-"""
-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
+import math
 
-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 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
+class FibonacciHeapNode:
+    def __init__(self, key, value=None):
+        self.key = key
+        self.value = value
+        self.degree = 0
         self.parent = None
         self.child = None
-        self.left = self
-        self.right = self
-        self.degree = 0
         self.mark = False
-
-    def add_sibling(self, node):
-        """
-        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
+        self.next = self
+        self.prev = self
 
     def add_child(self, node):
-        """
-        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
         else:
-            self.child.add_sibling(node)
+            node.prev = self.child
+            node.next = self.child.next
+            self.child.next.prev = node
+            self.child.next = node
+        node.parent = self
         self.degree += 1
 
-    def remove(self):
-        """Removes this node from its sibling list."""
-        self.left.right = self.right
-        self.right.left = self.left
+    def remove_child(self, node):
+        if node.next == node:  # Single child
+            self.child = None
+        elif self.child == node:
+            self.child = node.next
+        node.prev.next = node.next
+        node.next.prev = node.prev
+        node.parent = None
+        self.degree -= 1
 
 
 class FibonacciHeap:
-    """
-    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()
-    >>> 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
+        self.total_nodes = 0
 
     def is_empty(self):
-        """
-        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):
-        """
-        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:
+    def insert(self, key, value=None):
+        node = FibonacciHeapNode(key, value)
+        self._merge_with_root_list(node)
+        if not self.min_node or node.key < self.min_node.key:
             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
+        self.total_nodes += 1
         return node
 
-    def peek(self):
-        """
-        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):
-        """
-        Merges another Fibonacci heap into this one.
+    def find_min(self):
+        return self.min_node
 
-        Args:
-            other: Another FibonacciHeap instance to merge with this one.
-        """
-        if not other.min_node:
-            return
+    def union(self, other_heap):
+        if not other_heap.min_node:
+            return self
         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):
-        """
-        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)
+            self.min_node = other_heap.min_node
         else:
-            node2.child = node1
-        node1.parent = node2
-        node2.degree += 1
-        node1.mark = False
-
-    def delete_min(self):
-        """
-        Removes and returns the minimum value from the heap.
-
-        Returns:
-            The minimum value that was removed.
+            self._merge_with_root_list(other_heap.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
+
+    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)
 
-        Raises:
-            IndexError: If the heap is empty.
-        """
+    def _merge_with_root_list(self, node):
         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
+            self.min_node = node
+        else:
+            node.prev = self.min_node
+            node.next = self.min_node.next
+            self.min_node.next.prev = node
+            self.min_node.next = node
 
-        # Remove minimum node
-        if self.min_node.right == self.min_node:
+    def _remove_from_root_list(self, node):
+        if node.next == 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):
-        """
-        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
-        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
+            node.prev.next = node.next
+            node.next.prev = node.prev
+
+    def _consolidate(self):
+        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
         self.min_node = None
-        for degree in range(max_degree):
-            if degree_table[degree]:
+        for i in range(array_size):
+            if array[i]:
                 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
+                    self.min_node = array[i]
                 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):
-        """
-        Decreases the value of a node.
+                    self._merge_with_root_list(array[i])
+                    if array[i].key < self.min_node.key:
+                        self.min_node = array[i]
 
-        Args:
-            node: The node whose value should be decreased.
-            new_val: The new value for the node.
+    def _link(self, y, x):
+        self._remove_from_root_list(y)
+        x.add_child(y)
+        y.mark = False
 
-        Raises:
-            ValueError: If new value is greater than current value.
-        """
-        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):
-        """
-        Cuts a node from its parent
-
-        Args:
-            node: Node to be cut.
-            parent: Parent of the node to be cut.
-        """
-
-        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):
-        """
-        Performs cascading cut operation.
-
-        Args:
-            node: Starting node for cascading cut.
-        """
-
-        parent = node.parent
-        if parent:
-            if not node.mark:
-                node.mark = True
-            else:
-                self.__cut(node, parent)
-                self.__cascading_cut(parent)
-
-    def __str__(self):
-        """
-        Returns a string representation of the heap.
-
-        Returns:
-            str: A string showing the heap structure.
-        """
-        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))
+    def _iterate(self, start):
+        node = start
+        while True:
+            yield node
+            node = node.next
+            if node == start:
+                break
 
 
+# Example usage
 if __name__ == "__main__":
-    import doctest
-    doctest.testmod()
+    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

From 502953aa70bc4fbda148c666ffb3a3855e506221 Mon Sep 17 00:00:00 2001
From: mcawezome <20085898@tafe.wa.edu.au>
Date: Mon, 25 Nov 2024 11:57:05 +1100
Subject: [PATCH 13/15] Added full implementation of fibonacci heap

---
 data_structures/heap/fibonacci_heap.py | 339 ++++++++++++++++++++-----
 1 file changed, 271 insertions(+), 68 deletions(-)

diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py
index 68ad764bee0d..b1a182b3543d 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 @@ def __init__(self, key, value=None):
         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 @@ def add_child(self, node):
         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 @@ def remove_child(self, node):
 
 
 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,65 +152,129 @@ def insert(self, key, value=None):
         return node
 
     def find_min(self):
-        return self.min_node
+        """
+        Finds the node with the minimum key.
 
-    def union(self, other_heap):
-        if not other_heap.min_node:
-            return self
-        if not self.min_node:
-            self.min_node = other_heap.min_node
-        else:
-            self._merge_with_root_list(other_heap.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
+        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):
-        z = self.min_node
-        if z:
-            if z.child:
-                children = list(self._iterate(z.child))
+        """
+        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(z)
-            if z == z.next:
+            self._remove_from_root_list(temp_min_node)
+            if temp_min_node == temp_min_node.next:
                 self.min_node = None
             else:
-                self.min_node = z.next
+                self.min_node = temp_min_node.next
                 self._consolidate()
             self.total_nodes -= 1
-        return z
+        return temp_min_node
 
-    def decrease_key(self, x, new_key):
-        if new_key > x.key:
+    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")
-        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
+        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 _cut(self, x, y):
-        y.remove_child(x)
-        self._merge_with_root_list(x)
-        x.mark = False
+    def union(self, other_heap):
+        """
+        Merges another Fibonacci Heap into this heap.
 
-    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)
+        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
+        if not self.min_node:
+            self.min_node = other_heap.min_node
+        else:
+            self._merge_with_root_list(other_heap.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
 
     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 @@ def _merge_with_root_list(self, node):
             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 @@ def _remove_from_root_list(self, node):
             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 @@ def _consolidate(self):
                     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

From 7b987a0dd92e085ad8ae130ea94725118b83e72e Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 25 Nov 2024 02:01:03 +0000
Subject: [PATCH 14/15] [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
---
 data_structures/heap/fibonacci_heap.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py
index b1a182b3543d..d68fdde6b4d5 100644
--- a/data_structures/heap/fibonacci_heap.py
+++ b/data_structures/heap/fibonacci_heap.py
@@ -355,8 +355,7 @@ def _cascading_cut(self, node_to_cut):
         Args:
             node_to_cut (FibonacciHeapNode): The node to be cut recursively.
         """
-        temp_parent = node_to_cut.parent
-        if temp_parent:
+        if temp_parent := node_to_cut.parent:
             if not node_to_cut.mark:
                 node_to_cut.mark = True
             else:

From 660976f1c731b190d8f2c1b3d95eb5444dabdc84 Mon Sep 17 00:00:00 2001
From: mcawezome <20085898@tafe.wa.edu.au>
Date: Mon, 25 Nov 2024 13:10:34 +1100
Subject: [PATCH 15/15] Added complete implementation of Fibonacci Heap

---
 data_structures/heap/fibonacci_heap.py | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py
index d68fdde6b4d5..4caaa00fd59e 100644
--- a/data_structures/heap/fibonacci_heap.py
+++ b/data_structures/heap/fibonacci_heap.py
@@ -38,9 +38,11 @@ class FibonacciHeapNode:
         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.
+        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.
+        prev (FibonacciHeapNode): The previous sibling
+            in the circular doubly-linked list.
     """
 
     def __init__(self, key, value=None):