Rewrote entire file to pass checks

This commit is contained in:
mcawezome 2024-11-18 11:03:04 +08:00
parent 369994d334
commit ea5a187b0a

View File

@ -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: class Node:
"""A node in the Fibonacci heap. """
Node in a Fibonacci heap containing:
Each node maintains references to its key, degree (number of children), - value
marked status, parent, child, and circular linked list references (left/right). - parent, child, and sibling links
- degree (number of children)
Attributes: - mark (whether the node has lost a child since
key: The key value stored in the node becoming a child of its current parent)
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: float | None) -> None: def __init__(self, val):
self.key = key or None self.val = val
self.degree = 0 self.parent = None
self.marked = False self.child = None
self.parent = Node(None)
self.child = Node(None)
self.left = self self.left = self
self.right = 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: class FibonacciHeap:
"""Implementation of a Fibonacci heap using circular linked lists. """
Min-oriented Fibonacci heap implementation.
A Fibonacci heap is a collection of trees satisfying the min-heap property. Example:
This implementation uses circular linked lists for both the root list and >>> heap = FibonacciHeap()
child lists of nodes. >>> heap.insert(3)
>>> heap.insert(2)
Attributes: >>> heap.insert(15)
min_node: Reference to the node with minimum key >>> heap.peek()
total_nodes: Total number of nodes in the heap 2
>>> heap.delete_min()
Reference: Introduction to Algorithms (CLRS) Chapter 19 2
https://en.wikipedia.org/wiki/Fibonacci_heap >>> heap.peek()
3
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) -> None: def __init__(self):
self.min_node = Node(None) self.min_node = None
self.total_nodes = 0 self.size = 0
def insert(self, key: float | None) -> Node: def is_empty(self):
"""Insert a new key into the heap. """Return True if heap is empty"""
return self.min_node is None
Args: def insert(self, val):
key: The key value to insert """Insert a new key into the heap"""
node = Node(val)
Returns: if not self.min_node:
Node: The newly created node self.min_node = 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: else:
self._insert_into_circular_list(self.min_node, new_node) self.min_node.add_sibling(node)
if new_node.key < self.min_node.key: if node.val < self.min_node.val:
self.min_node = new_node self.min_node = node
self.total_nodes += 1 self.size += 1
return new_node return node
@staticmethod def peek(self):
def _insert_into_circular_list(base_node: Node, node_to_insert: Node) -> Node: """Return minimum value without removing it"""
"""Insert node into circular linked list. if not self.min_node:
raise IndexError("Heap is empty")
return self.min_node.val
Args: def merge_heaps(self, other):
base_node: The reference node in the circular list """Merge another Fibonacci heap with this one"""
node_to_insert: The node to insert into the list if not other.min_node:
return
if not self.min_node:
self.min_node = other.min_node
else:
# Merge root lists
self.min_node.right.left = other.min_node.left
other.min_node.left.right = self.min_node.right
self.min_node.right = other.min_node
other.min_node.left = self.min_node
Returns: if other.min_node.val < self.min_node.val:
Node: The base node self.min_node = other.min_node
Examples: self.size += other.size
>>> 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 def __link_trees(self, node1, node2):
node_to_insert.left = base_node """Link two trees of same degree"""
base_node.right.left = node_to_insert node1.remove()
base_node.right = node_to_insert if node2.child:
return base_node node2.child.add_sibling(node1)
else:
node2.child = node1
node1.parent = node2
node2.degree += 1
node1.mark = False
def extract_min(self) -> float | None: def delete_min(self):
"""Remove and return the minimum key from the heap. """Remove and return the minimum value"""
if not self.min_node:
raise IndexError("Heap is empty")
This operation removes the node with the minimum key from the heap, min_val = self.min_node.val
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: # Add all children to root list
Node: The minimum key value that was removed, if self.min_node.child:
or None if the heap is empty curr = self.min_node.child
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: while True:
next_child = current_child.right next_node = curr.right
self._insert_into_circular_list(self.min_node, current_child) curr.parent = None
current_child.parent.key = None curr.mark = False
if current_child == last_child: self.min_node.add_sibling(curr)
if curr.right == self.min_node.child:
break break
current_child = next_child curr = next_node
min_node.left.right = min_node.right # Remove minimum node
min_node.right.left = min_node.left if self.min_node.right == self.min_node:
self.min_node = None
if min_node == min_node.right:
self.min_node.key = None
else: else:
self.min_node = min_node.right self.min_node.remove()
self._consolidate() self.min_node = self.min_node.right
self.__consolidate()
self.total_nodes -= 1 self.size -= 1
return min_node.key return min_val
def _consolidate(self) -> None: def __consolidate(self):
"""Consolidate the heap after removing the minimum node. """Consolidate trees after delete_min"""
max_degree = int(self.size**0.5) + 1
This internal method maintains the Fibonacci heap properties by combining degree_table = [None] * max_degree
trees of the same degree until no two roots have the same degree. This
process is key to maintaining the efficiency of the data structure.
The consolidation process works by:
1. Creating a temporary array indexed by tree degree
2. Processing each root node and combining trees of the same degree
3. Reconstructing the root list and finding the new minimum
Time complexity: O(log n) amortized
Note:
This is an internal method called by extract_min and should not be
called directly from outside the class.
"""
max_degree = int(self.total_nodes**0.5) + 1
degree_table = [Node(None)] * max_degree
# Collect all roots
roots = [] roots = []
if self.min_node: curr = self.min_node
current_root = self.min_node while True:
while True: roots.append(curr)
roots.append(current_root) curr = curr.right
if current_root.right == self.min_node: if curr == self.min_node:
break break
current_root = current_root.right
for current_root in roots: # Consolidate trees
root_node = current_root for root in roots:
current_degree = root_node.degree degree = root.degree
while degree_table[degree]:
other = degree_table[degree]
if root.val > other.val:
root, other = other, root
self.__link_trees(other, root)
degree_table[degree] = None
degree += 1
degree_table[degree] = root
while degree_table[current_degree] is not None: # Find new minimum
other_root = degree_table[current_degree] self.min_node = None
if root_node.key > other_root.key:
root_node, other_root = other_root, root_node
other_root.left.right = other_root.right
other_root.right.left = other_root.left
if root_node.child.key is None:
root_node.child = other_root
other_root.right = other_root
other_root.left = other_root
else:
self._insert_into_circular_list(root_node.child, other_root)
other_root.parent = root_node
root_node.degree += 1
other_root.marked = False
degree_table[current_degree] = Node(None)
current_degree += 1
degree_table[current_degree] = root_node
self.min_node.key = None
for degree in range(max_degree): for degree in range(max_degree):
if degree_table[degree] is not None and ( if degree_table[degree]:
self.min_node is None or (degree_table[degree] < self.min_node.key) if not self.min_node:
): self.min_node = degree_table[degree]
self.min_node = degree_table[degree] self.min_node.left = self.min_node
self.min_node.right = self.min_node
else:
self.min_node.add_sibling(degree_table[degree])
if degree_table[degree].val < self.min_node.val:
self.min_node = degree_table[degree]
def decrease_key(self, node: Node, new_key: float | None) -> None: def decrease_key(self, node, new_val):
"""Decrease the key value of a given node. """Decrease the value of a node"""
if new_val > node.val:
raise ValueError("New value is greater than current value")
This operation updates the key of a node to a new, smaller value and node.val = new_val
maintains the min-heap property by potentially cutting the node from parent = node.parent
its parent and performing cascading cuts up the tree.
Args: if parent and node.val < parent.val:
node: The node whose key should be decreased self.__cut(node, parent)
new_key: The new key value, must be smaller than the current key self.__cascading_cut(parent)
Example: if node.val < self.min_node.val:
>>> 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:
self.min_node = node self.min_node = node
def _cut(self, child_node: Node, parent_node: Node) -> None: def __cut(self, node, parent):
"""Cut a node from its parent and add it to the root list. """Cut a node from its parent"""
parent.degree -= 1
if parent.child == node:
parent.child = node.right if node.right != node else None
node.remove()
node.left = node
node.right = node
node.parent = None
node.mark = False
self.min_node.add_sibling(node)
This is a helper method used in decrease_key operations. When a node's key def __cascading_cut(self, node):
becomes smaller than its parent's key, it needs to be cut from its parent """Perform cascading cut operation"""
and added to the root list to maintain the min-heap property. if parent := node.parent:
if not node.mark:
Args: node.mark = True
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
else: else:
self._cut(current_node, parent_node) self.__cut(node, parent)
self._cascading_cut(parent_node) self.__cascading_cut(parent)
def delete(self, node: Node) -> None: def __str__(self):
"""Delete a node from the heap. """String representation of the heap"""
if not self.min_node:
return "Empty heap"
This operation removes a given node from the heap by first decreasing def print_tree(node, level=0):
its key to negative infinity (making it the minimum) and then extracting result = []
the minimum. curr = node
while True:
result.append("-" * level + str(curr.val))
if curr.child:
result.extend(print_tree(curr.child, level + 1))
curr = curr.right
if curr == node:
break
return result
Args: return "\n".join(print_tree(self.min_node))
node: The node to be deleted from the heap
Example:
>>> heap = FibonacciHeap()
>>> node1 = heap.insert(3)
>>> node2 = heap.insert(2)
>>> heap.delete(node1)
>>> heap.find_min()
2
>>> heap.total_nodes
1
Note:
This operation has an amortized time complexity of O(log n)
as it combines decrease_key and extract_min operations.
"""
self.decrease_key(node, float("-inf"))
self.extract_min()
def find_min(self) -> float | None:
"""Return the minimum key without removing it from the heap.
This operation provides quick access to the minimum key in the heap
without modifying the heap structure.
Returns:
float | None: The minimum key value, or None if the heap is empty
Example:
>>> heap = FibonacciHeap()
>>> heap.find_min() is None
True
>>> node1 = heap.insert(3)
>>> heap.find_min()
3
"""
return self.min_node.key if self.min_node else Node(None).key
def is_empty(self) -> bool:
"""Check if heap is empty.
Returns:
bool: True if heap is empty, False otherwise
Examples:
>>> heap = FibonacciHeap()
>>> heap.is_empty()
True
>>> node = heap.insert(1)
>>> heap.is_empty()
False
"""
return self.min_node.key is None
def merge(self, other_heap: FibonacciHeap) -> None:
"""Merge another Fibonacci heap into this one.
This operation combines two Fibonacci heaps by concatenating their
root lists and updating the minimum pointer if necessary. The other
heap is effectively consumed in this process.
Args:
other_heap: Another FibonacciHeap instance to merge into this one
Example:
>>> heap1 = FibonacciHeap()
>>> node1 = heap1.insert(3)
>>> heap2 = FibonacciHeap()
>>> node2 = heap2.insert(2)
>>> heap1.merge(heap2)
>>> heap1.find_min()
2
>>> heap1.total_nodes
2
"""
if other_heap.min_node.key is None:
return
if self.min_node.key is None:
self.min_node = other_heap.min_node
else:
self.min_node.right.left = other_heap.min_node.left
other_heap.min_node.left.right = self.min_node.right
self.min_node.right = other_heap.min_node
other_heap.min_node.left = self.min_node
if other_heap.min_node.key < self.min_node.key:
self.min_node = other_heap.min_node
self.total_nodes += other_heap.total_nodes
if __name__ == "__main__": if __name__ == "__main__":