From b4e0306de0d80f45e40b2722c0cb5db5761049e1 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Sun, 1 Oct 2023 16:51:43 +0530 Subject: [PATCH 1/6] Add fibonacci_heap.py --- data_structures/heap/fibonacci_heap.py | 248 +++++++++++++++++++++++++ 1 file changed, 248 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..3989ed1e7809 --- /dev/null +++ b/data_structures/heap/fibonacci_heap.py @@ -0,0 +1,248 @@ +from math import log + + +class FibonacciNode: + """ + Node in a Fibonacci Heap, containing: + - value + - degree (number of children) + - child (a child node) + - left and right pointers to neighboring nodes + - parent (pointer to the parent node) + - mark (boolean flag to indicate if the node has lost a child) + """ + + def __init__(self, val): + """ + Initialize a FibonacciNode with the given value. + """ + self.val = val + self.degree = 0 + self.child = None + self.left = self + self.right = self + self.parent = None + self.mark = False + + def add_child(self, child): + """ + Add a child to this node. + """ + if self.child is None: + self.child = child + else: + child.right = self.child + child.left = self.child.left + self.child.left.right = child + self.child.left = child + child.parent = self + self.degree += 1 + + def remove_child(self, child): + """ + Remove a child from this node's children. + """ + if child == self.child: + if child.right == child: + self.child = None + else: + self.child = child.right + child.left.right = child.right + child.right.left = child.left + child.parent = None + child.mark = False + self.degree -= 1 + + +class FibonacciHeap: + """ + Min-oriented priority queue implemented with the Fibonacci Heap data structure. + It supports: + - Insert element: O(1) + - Find minimum: O(1) + - Merge (meld) heaps: O(1) + - Delete minimum: Amortized O(log n) + - Decrease key: Amortized O(1) + - Delete node: Amortized O(log n) + """ + + def __init__(self): + """ + Initialize an empty Fibonacci Heap. + """ + self.min_node = None + self.root_list = [] + self.size = 0 + + def insert(self, val): + """ + Insert a new element with the given value into the Fibonacci Heap. + """ + new_node = FibonacciNode(val) + if self.min_node is None: + self.min_node = new_node + else: + self._link(self.min_node, new_node) + if val < self.min_node.val: + self.min_node = new_node + self.root_list.append(new_node) + self.size += 1 + + def _link(self, min_node, new_node): + """ + Link two nodes in the Fibonacci Heap. + """ + min_node.add_child(new_node) + + def find_min(self): + """ + Find the minimum element in the Fibonacci Heap. + """ + if self.min_node is None: + raise Exception("Heap is empty") + return self.min_node.val + + def _consolidate(self): + """ + Consolidate nodes with the same degree in the Fibonacci Heap. + """ + max_degree = int(2 * log(self.size) / log(1.618)) + degree_table = [None] * (max_degree + 1) + + current = self.root_list[:] + for node in current: + degree = node.degree + while degree_table[degree]: + other = degree_table[degree] + if node.val > other.val: + node, other = other, node + self._link(other, node) + degree_table[degree] = None + degree += 1 + degree_table[degree] = node + + self.min_node = None + for node in degree_table: + if node and (self.min_node is None or node.val < self.min_node.val): + self.min_node = node + + def delete_min(self): + """ + Delete the minimum element from the Fibonacci Heap. + """ + if self.min_node is None: + return None + + min_val = self.min_node.val + + if self.min_node.child: + children = self.min_node.child + while True: + next_child = children.right + children.parent = None + self.root_list.append(children) + if next_child == self.min_node.child: + break + children = next_child + + self.root_list.remove(self.min_node) + self._consolidate() + self._update_min_node() + self.size -= 1 + + return min_val + + def decrease_key(self, node, new_val): + """ + Decrease the key of a node in the Fibonacci Heap. + """ + 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): + """ + Cut a node from its parent in the Fibonacci Heap. + """ + parent.remove_child(node) + self.root_list.append(node) + node.mark = False + + def _cascading_cut(self, node): + """ + Perform a cascading cut operation in the Fibonacci Heap. + """ + parent = node.parent + if parent: + if not node.mark: + node.mark = True + else: + self._cut(node, parent) + self._cascading_cut(parent) + + def delete_node(self, node): + """ + Delete a specific node from the Fibonacci Heap. + """ + self.decrease_key(node, float("-inf")) + self.delete_min() + + def _update_min_node(self): + """ + Update the minimum node in the Fibonacci Heap. + """ + if not self.root_list: + self.min_node = None + return + + min_val = min(node.val for node in self.root_list) + self.min_node = next( + node for node in self.root_list if node.val == min_val) + + def __str__(self): + """ + Return a string representation of the Fibonacci Heap. + """ + if self.min_node is None: + return "Fibonacci Heap (Empty)" + return f"Fibonacci Heap (Minimum: {self.min_node.val})" + + +# Unit Tests +if __name__ == "__main__": + import random + + # Create a Fibonacci Heap + fib_heap = FibonacciHeap() + + # Insert random values + for _ in range(10): + val = random.randint(1, 100) + fib_heap.insert(val) + + print(fib_heap) + print("Minimum:", fib_heap.find_min()) + + # Delete minimum + min_val = fib_heap.delete_min() + print("Deleted Minimum:", min_val) + print(fib_heap) + + # Decrease a key + node_to_decrease = fib_heap.root_list[2] + new_val = random.randint(1, 10) + print("Decreasing key of node", node_to_decrease.val, "to", new_val) + fib_heap.decrease_key(node_to_decrease, new_val) + print(fib_heap) + + # Delete a node + node_to_delete = fib_heap.root_list[4] + print("Deleting node", node_to_delete.val) + fib_heap.delete_node(node_to_delete) + print(fib_heap) From b74a385bf37a811ccce38236589910e0db0993a0 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Sun, 1 Oct 2023 16:56:25 +0530 Subject: [PATCH 2/6] Add doctest and Wiki URL --- data_structures/heap/fibonacci_heap.py | 35 +++----------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py index 3989ed1e7809..709aec94ea72 100644 --- a/data_structures/heap/fibonacci_heap.py +++ b/data_structures/heap/fibonacci_heap.py @@ -64,6 +64,8 @@ class FibonacciHeap: - Delete minimum: Amortized O(log n) - Decrease key: Amortized O(1) - Delete node: Amortized O(log n) + + For more details, refer to the Wikipedia page on [Fibonacci Heap](https://en.wikipedia.org/wiki/Fibonacci_heap). """ def __init__(self): @@ -214,35 +216,6 @@ def __str__(self): return f"Fibonacci Heap (Minimum: {self.min_node.val})" -# Unit Tests if __name__ == "__main__": - import random - - # Create a Fibonacci Heap - fib_heap = FibonacciHeap() - - # Insert random values - for _ in range(10): - val = random.randint(1, 100) - fib_heap.insert(val) - - print(fib_heap) - print("Minimum:", fib_heap.find_min()) - - # Delete minimum - min_val = fib_heap.delete_min() - print("Deleted Minimum:", min_val) - print(fib_heap) - - # Decrease a key - node_to_decrease = fib_heap.root_list[2] - new_val = random.randint(1, 10) - print("Decreasing key of node", node_to_decrease.val, "to", new_val) - fib_heap.decrease_key(node_to_decrease, new_val) - print(fib_heap) - - # Delete a node - node_to_delete = fib_heap.root_list[4] - print("Deleting node", node_to_delete.val) - fib_heap.delete_node(node_to_delete) - print(fib_heap) + import doctest + doctest.testmod() From 592b8d3ac29c3ea68fd4ede511aea14609be436a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 11:27:47 +0000 Subject: [PATCH 3/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- data_structures/heap/fibonacci_heap.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py index 709aec94ea72..745105dfc1a0 100644 --- a/data_structures/heap/fibonacci_heap.py +++ b/data_structures/heap/fibonacci_heap.py @@ -180,8 +180,7 @@ def _cascading_cut(self, node): """ Perform a cascading cut operation in the Fibonacci Heap. """ - parent = node.parent - if parent: + if parent := node.parent: if not node.mark: node.mark = True else: @@ -204,8 +203,7 @@ def _update_min_node(self): return min_val = min(node.val for node in self.root_list) - self.min_node = next( - node for node in self.root_list if node.val == min_val) + self.min_node = next(node for node in self.root_list if node.val == min_val) def __str__(self): """ @@ -218,4 +216,5 @@ def __str__(self): if __name__ == "__main__": import doctest + doctest.testmod() From cc459b317b4882851b0eb1a562640f7431aaeac6 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Sun, 1 Oct 2023 17:01:22 +0530 Subject: [PATCH 4/6] Add function return types --- data_structures/heap/fibonacci_heap.py | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py index 709aec94ea72..40af739b9bf6 100644 --- a/data_structures/heap/fibonacci_heap.py +++ b/data_structures/heap/fibonacci_heap.py @@ -12,7 +12,7 @@ class FibonacciNode: - mark (boolean flag to indicate if the node has lost a child) """ - def __init__(self, val): + def __init__(self, val: int) -> None: """ Initialize a FibonacciNode with the given value. """ @@ -24,7 +24,7 @@ def __init__(self, val): self.parent = None self.mark = False - def add_child(self, child): + def add_child(self, child: "FibonacciNode") -> None: """ Add a child to this node. """ @@ -38,7 +38,7 @@ def add_child(self, child): child.parent = self self.degree += 1 - def remove_child(self, child): + def remove_child(self, child: "FibonacciNode") -> None: """ Remove a child from this node's children. """ @@ -68,7 +68,7 @@ class FibonacciHeap: For more details, refer to the Wikipedia page on [Fibonacci Heap](https://en.wikipedia.org/wiki/Fibonacci_heap). """ - def __init__(self): + def __init__(self) -> None: """ Initialize an empty Fibonacci Heap. """ @@ -76,7 +76,7 @@ def __init__(self): self.root_list = [] self.size = 0 - def insert(self, val): + def insert(self, val: int) -> None: """ Insert a new element with the given value into the Fibonacci Heap. """ @@ -90,13 +90,13 @@ def insert(self, val): self.root_list.append(new_node) self.size += 1 - def _link(self, min_node, new_node): + def _link(self, min_node: FibonacciNode, new_node: FibonacciNode) -> None: """ Link two nodes in the Fibonacci Heap. """ min_node.add_child(new_node) - def find_min(self): + def find_min(self) -> int: """ Find the minimum element in the Fibonacci Heap. """ @@ -104,7 +104,7 @@ def find_min(self): raise Exception("Heap is empty") return self.min_node.val - def _consolidate(self): + def _consolidate(self) -> None: """ Consolidate nodes with the same degree in the Fibonacci Heap. """ @@ -128,7 +128,7 @@ def _consolidate(self): if node and (self.min_node is None or node.val < self.min_node.val): self.min_node = node - def delete_min(self): + def delete_min(self) -> int: """ Delete the minimum element from the Fibonacci Heap. """ @@ -154,7 +154,7 @@ def delete_min(self): return min_val - def decrease_key(self, node, new_val): + def decrease_key(self, node: FibonacciNode, new_val: int) -> None: """ Decrease the key of a node in the Fibonacci Heap. """ @@ -168,7 +168,7 @@ def decrease_key(self, node, new_val): if node.val < self.min_node.val: self.min_node = node - def _cut(self, node, parent): + def _cut(self, node: FibonacciNode, parent: FibonacciNode) -> None: """ Cut a node from its parent in the Fibonacci Heap. """ @@ -176,7 +176,7 @@ def _cut(self, node, parent): self.root_list.append(node) node.mark = False - def _cascading_cut(self, node): + def _cascading_cut(self, node: FibonacciNode) -> None: """ Perform a cascading cut operation in the Fibonacci Heap. """ @@ -188,14 +188,14 @@ def _cascading_cut(self, node): self._cut(node, parent) self._cascading_cut(parent) - def delete_node(self, node): + def delete_node(self, node: FibonacciNode) -> None: """ Delete a specific node from the Fibonacci Heap. """ self.decrease_key(node, float("-inf")) self.delete_min() - def _update_min_node(self): + def _update_min_node(self) -> None: """ Update the minimum node in the Fibonacci Heap. """ @@ -207,7 +207,7 @@ def _update_min_node(self): self.min_node = next( node for node in self.root_list if node.val == min_val) - def __str__(self): + def __str__(self) -> str: """ Return a string representation of the Fibonacci Heap. """ From 60fb9f3a2023b81add37d33e6cde7500a7028d15 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Sun, 1 Oct 2023 17:06:42 +0530 Subject: [PATCH 5/6] Add test --- data_structures/heap/fibonacci_heap.py | 35 ++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/data_structures/heap/fibonacci_heap.py b/data_structures/heap/fibonacci_heap.py index 3657af7818a4..79d1aae044f3 100644 --- a/data_structures/heap/fibonacci_heap.py +++ b/data_structures/heap/fibonacci_heap.py @@ -24,9 +24,39 @@ def __init__(self, val: int) -> None: self.parent = None self.mark = False - def add_child(self, child: "FibonacciNode") -> None: + def add_child(self, child): """ Add a child to this node. + + >>> parent = FibonacciNode(5) + >>> child1 = FibonacciNode(3) + >>> child2 = FibonacciNode(7) + >>> parent.add_child(child1) + >>> parent.child + <__main__.FibonacciNode object at ...> + >>> parent.child.val + 3 + >>> parent.child.right.val + 3 + >>> parent.child.left.val + 3 + >>> parent.child.parent + <__main__.FibonacciNode object at ...> + >>> parent.child.degree + 1 + >>> parent.add_child(child2) + >>> parent.child.right.val + 7 + >>> parent.child.left.val + 3 + >>> child1.parent + <__main__.FibonacciNode object at ...> + >>> child1.left.val + 7 + >>> child1.right.val + 7 + >>> child1.degree + 0 """ if self.child is None: self.child = child @@ -203,7 +233,8 @@ def _update_min_node(self) -> None: return min_val = min(node.val for node in self.root_list) - self.min_node = next(node for node in self.root_list if node.val == min_val) + self.min_node = next( + node for node in self.root_list if node.val == min_val) def __str__(self) -> str: """ From df95323a89a0c126484ce2bb2763ded6727a9185 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 11:37:18 +0000 Subject: [PATCH 6/6] [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 79d1aae044f3..674546808651 100644 --- a/data_structures/heap/fibonacci_heap.py +++ b/data_structures/heap/fibonacci_heap.py @@ -233,8 +233,7 @@ def _update_min_node(self) -> None: return min_val = min(node.val for node in self.root_list) - self.min_node = next( - node for node in self.root_list if node.val == min_val) + self.min_node = next(node for node in self.root_list if node.val == min_val) def __str__(self) -> str: """