| """ |
| Min-heaps. |
| """ |
|
|
| from heapq import heappop, heappush |
| from itertools import count |
|
|
| import networkx as nx |
|
|
| __all__ = ["MinHeap", "PairingHeap", "BinaryHeap"] |
|
|
|
|
| class MinHeap: |
| """Base class for min-heaps. |
| |
| A MinHeap stores a collection of key-value pairs ordered by their values. |
| It supports querying the minimum pair, inserting a new pair, decreasing the |
| value in an existing pair and deleting the minimum pair. |
| """ |
|
|
| class _Item: |
| """Used by subclassess to represent a key-value pair.""" |
|
|
| __slots__ = ("key", "value") |
|
|
| def __init__(self, key, value): |
| self.key = key |
| self.value = value |
|
|
| def __repr__(self): |
| return repr((self.key, self.value)) |
|
|
| def __init__(self): |
| """Initialize a new min-heap.""" |
| self._dict = {} |
|
|
| def min(self): |
| """Query the minimum key-value pair. |
| |
| Returns |
| ------- |
| key, value : tuple |
| The key-value pair with the minimum value in the heap. |
| |
| Raises |
| ------ |
| NetworkXError |
| If the heap is empty. |
| """ |
| raise NotImplementedError |
|
|
| def pop(self): |
| """Delete the minimum pair in the heap. |
| |
| Returns |
| ------- |
| key, value : tuple |
| The key-value pair with the minimum value in the heap. |
| |
| Raises |
| ------ |
| NetworkXError |
| If the heap is empty. |
| """ |
| raise NotImplementedError |
|
|
| def get(self, key, default=None): |
| """Returns the value associated with a key. |
| |
| Parameters |
| ---------- |
| key : hashable object |
| The key to be looked up. |
| |
| default : object |
| Default value to return if the key is not present in the heap. |
| Default value: None. |
| |
| Returns |
| ------- |
| value : object. |
| The value associated with the key. |
| """ |
| raise NotImplementedError |
|
|
| def insert(self, key, value, allow_increase=False): |
| """Insert a new key-value pair or modify the value in an existing |
| pair. |
| |
| Parameters |
| ---------- |
| key : hashable object |
| The key. |
| |
| value : object comparable with existing values. |
| The value. |
| |
| allow_increase : bool |
| Whether the value is allowed to increase. If False, attempts to |
| increase an existing value have no effect. Default value: False. |
| |
| Returns |
| ------- |
| decreased : bool |
| True if a pair is inserted or the existing value is decreased. |
| """ |
| raise NotImplementedError |
|
|
| def __nonzero__(self): |
| """Returns whether the heap if empty.""" |
| return bool(self._dict) |
|
|
| def __bool__(self): |
| """Returns whether the heap if empty.""" |
| return bool(self._dict) |
|
|
| def __len__(self): |
| """Returns the number of key-value pairs in the heap.""" |
| return len(self._dict) |
|
|
| def __contains__(self, key): |
| """Returns whether a key exists in the heap. |
| |
| Parameters |
| ---------- |
| key : any hashable object. |
| The key to be looked up. |
| """ |
| return key in self._dict |
|
|
|
|
| class PairingHeap(MinHeap): |
| """A pairing heap.""" |
|
|
| class _Node(MinHeap._Item): |
| """A node in a pairing heap. |
| |
| A tree in a pairing heap is stored using the left-child, right-sibling |
| representation. |
| """ |
|
|
| __slots__ = ("left", "next", "prev", "parent") |
|
|
| def __init__(self, key, value): |
| super().__init__(key, value) |
| |
| self.left = None |
| |
| self.next = None |
| |
| self.prev = None |
| |
| self.parent = None |
|
|
| def __init__(self): |
| """Initialize a pairing heap.""" |
| super().__init__() |
| self._root = None |
|
|
| def min(self): |
| if self._root is None: |
| raise nx.NetworkXError("heap is empty.") |
| return (self._root.key, self._root.value) |
|
|
| def pop(self): |
| if self._root is None: |
| raise nx.NetworkXError("heap is empty.") |
| min_node = self._root |
| self._root = self._merge_children(self._root) |
| del self._dict[min_node.key] |
| return (min_node.key, min_node.value) |
|
|
| def get(self, key, default=None): |
| node = self._dict.get(key) |
| return node.value if node is not None else default |
|
|
| def insert(self, key, value, allow_increase=False): |
| node = self._dict.get(key) |
| root = self._root |
| if node is not None: |
| if value < node.value: |
| node.value = value |
| if node is not root and value < node.parent.value: |
| self._cut(node) |
| self._root = self._link(root, node) |
| return True |
| elif allow_increase and value > node.value: |
| node.value = value |
| child = self._merge_children(node) |
| |
| |
| if child is not None: |
| self._root = self._link(self._root, child) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| return False |
| else: |
| |
| node = self._Node(key, value) |
| self._dict[key] = node |
| self._root = self._link(root, node) if root is not None else node |
| return True |
|
|
| def _link(self, root, other): |
| """Link two nodes, making the one with the smaller value the parent of |
| the other. |
| """ |
| if other.value < root.value: |
| root, other = other, root |
| next = root.left |
| other.next = next |
| if next is not None: |
| next.prev = other |
| other.prev = None |
| root.left = other |
| other.parent = root |
| return root |
|
|
| def _merge_children(self, root): |
| """Merge the subtrees of the root using the standard two-pass method. |
| The resulting subtree is detached from the root. |
| """ |
| node = root.left |
| root.left = None |
| if node is not None: |
| link = self._link |
| |
| |
| |
| |
| prev = None |
| while True: |
| next = node.next |
| if next is None: |
| node.prev = prev |
| break |
| next_next = next.next |
| node = link(node, next) |
| node.prev = prev |
| prev = node |
| if next_next is None: |
| break |
| node = next_next |
| |
| |
| prev = node.prev |
| while prev is not None: |
| prev_prev = prev.prev |
| node = link(prev, node) |
| prev = prev_prev |
| |
| node.prev = None |
| node.next = None |
| node.parent = None |
| return node |
|
|
| def _cut(self, node): |
| """Cut a node from its parent.""" |
| prev = node.prev |
| next = node.next |
| if prev is not None: |
| prev.next = next |
| else: |
| node.parent.left = next |
| node.prev = None |
| if next is not None: |
| next.prev = prev |
| node.next = None |
| node.parent = None |
|
|
|
|
| class BinaryHeap(MinHeap): |
| """A binary heap.""" |
|
|
| def __init__(self): |
| """Initialize a binary heap.""" |
| super().__init__() |
| self._heap = [] |
| self._count = count() |
|
|
| def min(self): |
| dict = self._dict |
| if not dict: |
| raise nx.NetworkXError("heap is empty") |
| heap = self._heap |
| |
| |
| while True: |
| value, _, key = heap[0] |
| if key in dict and value == dict[key]: |
| break |
| heappop(heap) |
| return (key, value) |
|
|
| def pop(self): |
| dict = self._dict |
| if not dict: |
| raise nx.NetworkXError("heap is empty") |
| heap = self._heap |
| |
| |
| while True: |
| value, _, key = heap[0] |
| heappop(heap) |
| if key in dict and value == dict[key]: |
| break |
| del dict[key] |
| return (key, value) |
|
|
| def get(self, key, default=None): |
| return self._dict.get(key, default) |
|
|
| def insert(self, key, value, allow_increase=False): |
| dict = self._dict |
| if key in dict: |
| old_value = dict[key] |
| if value < old_value or (allow_increase and value > old_value): |
| |
| |
| |
| |
| dict[key] = value |
| heappush(self._heap, (value, next(self._count), key)) |
| return value < old_value |
| return False |
| else: |
| dict[key] = value |
| heappush(self._heap, (value, next(self._count), key)) |
| return True |
|
|