| | 1 | | using MoreStructures.PriorityQueues.FibonacciHeap; |
| | 2 | |
|
| | 3 | | namespace MoreStructures.PriorityQueues.BinomialHeap; |
| | 4 | |
|
| | 5 | | /// <summary> |
| | 6 | | /// A refinement of <see cref="BinomialHeapPriorityQueue{T}"/> which supports <see cref="IUpdatablePriorityQueue{T}"/> |
| | 7 | | /// operations, such as retrieval and update of priorities and removal of items. |
| | 8 | | /// </summary> |
| | 9 | | /// <remarks> |
| | 10 | | /// Check <see cref="DuplicatedItemsResolution{T, THeap}"/> for detailed informations about how the mapping between |
| | 11 | | /// items of type <typeparamref name="T"/> and heap nodes of type <see cref="TreeNode{T}"/> is performed, in presence |
| | 12 | | /// of duplicates. |
| | 13 | | /// </remarks> |
| | 14 | | public sealed class UpdatableBinomialHeapPriorityQueue<T> : BinomialHeapPriorityQueue<T>, IUpdatablePriorityQueue<T> |
| | 15 | | where T : notnull |
| | 16 | | { |
| 13895 | 17 | | private DuplicatedItemsResolution<T, BinomialHeapPriorityQueue<int>> DuplicatedItemsResolution { get; } = new(); |
| | 18 | |
|
| | 19 | | #region Public API |
| | 20 | |
|
| | 21 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 22 | | /// <remarks> |
| | 23 | | /// Clears the <see cref="BinomialHeapPriorityQueue{T}"/> structures and the additional |
| | 24 | | /// <see cref="DuplicatedItemsResolution{TItems, THeap}"/> object introduced to support updates and deletions. |
| | 25 | | /// <br/> |
| | 26 | | /// Time and Space Complexity is O(1). |
| | 27 | | /// </remarks> |
| | 28 | | public override void Clear() |
| 171 | 29 | | { |
| 171 | 30 | | base.Clear(); |
| 171 | 31 | | DuplicatedItemsResolution.Clear(); |
| 171 | 32 | | } |
| | 33 | |
|
| | 34 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 35 | | /// <remarks> |
| | 36 | | /// <inheritdoc cref="DuplicatedItemsResolution{T, THeap}.GetPrioritiesOf(T)"/> |
| | 37 | | /// </remarks> |
| 24 | 38 | | public IEnumerable<int> GetPrioritiesOf(T item) => DuplicatedItemsResolution.GetPrioritiesOf(item); |
| | 39 | |
|
| | 40 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 41 | | /// <remarks> |
| | 42 | | /// <para id="algorithm"> |
| | 43 | | /// ALGORITHM |
| | 44 | | /// <br/> |
| | 45 | | /// - It first retrieves the max priority P from the max priority item the queue via |
| | 46 | | /// <see cref="BinomialHeapPriorityQueue{T}.Peek"/>. |
| | 47 | | /// <br/> |
| | 48 | | /// - Then, it updates the priority of the provided <paramref name="item"/> via |
| | 49 | | /// <see cref="UpdatePriority(T, int)"/>, setting it to P + 1 and making <paramref name="item"/> the one with |
| | 50 | | /// max priority. |
| | 51 | | /// <br/> |
| | 52 | | /// - Finally it pops the <paramref name="item"/> via <see cref="BinomialHeapPriorityQueue{T}.Pop"/>. |
| | 53 | | /// </para> |
| | 54 | | /// <para id="complexity"> |
| | 55 | | /// COMPLEXITY |
| | 56 | | /// <br/> |
| | 57 | | /// - <see cref="BinomialHeapPriorityQueue{T}.Peek"/> has constant Time and Space Complexity. |
| | 58 | | /// <br/> |
| | 59 | | /// - However, <see cref="BinomialHeapPriorityQueue{T}.Pop"/> and <see cref="UpdatePriority(T, int)"/> have |
| | 60 | | /// logarithmic Time Complexity. |
| | 61 | | /// <br/> |
| | 62 | | /// - Therefore, Time Complexity is O(log(n) + dup_factor) and Space Complexity is O(1). |
| | 63 | | /// </para> |
| | 64 | | /// </remarks> |
| | 65 | | public PrioritizedItem<T>? Remove(T item) |
| 2910 | 66 | | { |
| 2910 | 67 | | if (Count == 0) |
| 1923 | 68 | | return null; |
| | 69 | |
|
| 987 | 70 | | var maxPrioritizedItem = Peek(); |
| 987 | 71 | | var treeNodeInBinomialHeap = DuplicatedItemsResolution.FindTreeNode(item); |
| 987 | 72 | | if (treeNodeInBinomialHeap == null) |
| 865 | 73 | | return null; |
| | 74 | |
|
| 122 | 75 | | var oldPrioritizedItem = treeNodeInBinomialHeap.PrioritizedItem; |
| 122 | 76 | | UpdatePriority(treeNodeInBinomialHeap, maxPrioritizedItem.Priority + 1, oldPrioritizedItem.PushTimestamp); |
| 122 | 77 | | Pop(); |
| 122 | 78 | | return oldPrioritizedItem; |
| 2910 | 79 | | } |
| | 80 | |
|
| | 81 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 82 | | /// <remarks> |
| | 83 | | /// <inheritdoc cref="DuplicatedItemsResolution{T, THeap}.FindTreeNode(T)"/> |
| | 84 | | /// <para id="algorithm-update"> |
| | 85 | | /// ALGORITHM - BINOMIAL HEAP UPDATE PART |
| | 86 | | /// <br/> |
| | 87 | | /// - When the priority is higher or equal, the value of priority is updated and the node is sifted up the |
| | 88 | | /// tree, until the heap property is restored. If the root of the tree is reached, the reference to the max |
| | 89 | | /// priority is checked and potentially updated. |
| | 90 | | /// <br/> |
| | 91 | | /// - When the priority is lower, the value of priority is updated and the node is sifted down the tree, until |
| | 92 | | /// the heap property is restored. If the node was a root, a linear scan of all the roots is made, to update |
| | 93 | | /// the reference to the max priority, which may have changed due to the decrease in priority. |
| | 94 | | /// <br/> |
| | 95 | | /// - No merging is required, since no node has been added or removed from any of the tree of the heap forest. |
| | 96 | | /// Nodes have been swapped by sift up or sift down procedures, but the shape of each tree, and so the |
| | 97 | | /// layout of the forest, hasn't changed. Therefore the binomial heap shape hasn't been violated. |
| | 98 | | /// <br/> |
| | 99 | | /// - Finally, the <see cref="PrioritizedItem{T}"/> before the update is returned as result. |
| | 100 | | /// </para> |
| | 101 | | /// <para id="complexity-update"> |
| | 102 | | /// COMPLEXITY - BINOMIAL HEAP UPDATE PART |
| | 103 | | /// <br/> |
| | 104 | | /// - Sift up, sift down and linear scan of roots are all logarithmic operations in time and constant in space. |
| | 105 | | /// <br/> |
| | 106 | | /// - Therefore, Time Complexity is O(log(n)) and Space Complexity is O(1). |
| | 107 | | /// </para> |
| | 108 | | /// <para id="complexity"> |
| | 109 | | /// COMPLEXITY - OVERALL |
| | 110 | | /// <br/> |
| | 111 | | /// - Time Complexity is O(log(n) + dup_factor) and Space Complexity is O(1). |
| | 112 | | /// <br/> |
| | 113 | | /// - Notice how this is higher than the Time Complexity for the corresponding functionality in a Fibonacci |
| | 114 | | /// Heap, which supports both pushing and priority updating in constant time. |
| | 115 | | /// </para> |
| | 116 | | /// </remarks> |
| | 117 | | public PrioritizedItem<T> UpdatePriority(T item, int newPriority) |
| 93 | 118 | | { |
| 93 | 119 | | var treeNodeInBinomialHeap = DuplicatedItemsResolution.FindTreeNode(item); |
| 93 | 120 | | if (treeNodeInBinomialHeap == null) |
| 3 | 121 | | throw new InvalidOperationException("The specified item is not in the queue."); |
| 90 | 122 | | return UpdatePriority(treeNodeInBinomialHeap, newPriority, CurrentPushTimestamp++); |
| 90 | 123 | | } |
| | 124 | |
|
| | 125 | | #endregion |
| | 126 | |
|
| | 127 | | #region Hooks |
| | 128 | |
|
| | 129 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 130 | | /// <remarks> |
| | 131 | | /// Hands over to <see cref="DuplicatedItemsResolution{T, THeap}.RaiseItemPushed(TreeNode{T})"/>. |
| | 132 | | /// </remarks> |
| | 133 | | protected override void RaiseItemPushed(TreeNode<T> newRoot) => |
| 6769 | 134 | | DuplicatedItemsResolution.RaiseItemPushed(newRoot); |
| | 135 | |
|
| | 136 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 137 | | /// <remarks> |
| | 138 | | /// Hands over to <see cref="DuplicatedItemsResolution{T, THeap}.RaiseItemPopping(TreeNode{T})"/>. |
| | 139 | | /// </remarks> |
| | 140 | | protected override void RaiseItemPopping(TreeNode<T> root) => |
| 3792 | 141 | | DuplicatedItemsResolution.RaiseItemPopping(root); |
| | 142 | |
|
| | 143 | | /// <summary> |
| | 144 | | /// Invoked just after the priority of the <see cref="PrioritizedItem{T}"/> of a <see cref="TreeNode{T}"/> in the |
| | 145 | | /// heap has changed. |
| | 146 | | /// </summary> |
| | 147 | | /// <param name="treeNode">The node whose item has changed priority.</param> |
| | 148 | | /// <param name="itemBefore">The <see cref="PrioritizedItem{T}"/> as it was before the change.</param> |
| | 149 | | /// <remarks> |
| | 150 | | /// Hands over to |
| | 151 | | /// <see cref="DuplicatedItemsResolution{T, THeap}.RaiseItemPriorityChanged(TreeNode{T}, PrioritizedItem{T})"/>. |
| | 152 | | /// </remarks> |
| | 153 | | private void RaiseItemPriorityChanged(TreeNode<T> treeNode, PrioritizedItem<T> itemBefore) => |
| 212 | 154 | | DuplicatedItemsResolution.RaiseItemPriorityChanged(treeNode, itemBefore); |
| | 155 | |
|
| | 156 | | /// <summary> |
| | 157 | | /// Invoked just after two items have had their <see cref="PrioritizedItem{T}"/> swapped. |
| | 158 | | /// </summary> |
| | 159 | | /// <param name="treeNode1">The first node.</param> |
| | 160 | | /// <param name="treeNode2">The second node.</param> |
| | 161 | | /// <remarks> |
| | 162 | | /// Hands over to |
| | 163 | | /// <see cref="DuplicatedItemsResolution{T, THeap}.RaiseItemsSwapped(TreeNode{T}, TreeNode{T})"/>. |
| | 164 | | /// </remarks> |
| | 165 | | private void RaiseItemsSwapped(TreeNode<T> treeNode1, TreeNode<T> treeNode2) => |
| 107 | 166 | | DuplicatedItemsResolution.RaiseItemsSwapped(treeNode1, treeNode2); |
| | 167 | |
|
| | 168 | | #endregion |
| | 169 | |
|
| | 170 | | #region Helpers |
| | 171 | |
|
| | 172 | | private PrioritizedItem<T> UpdatePriority(TreeNode<T> treeNode, int newPriority, int newPushTimestamp) |
| 212 | 173 | | { |
| 212 | 174 | | var newPrioritizedItem = |
| 212 | 175 | | new PrioritizedItem<T>(treeNode.PrioritizedItem.Item, newPriority, newPushTimestamp, PushTimestampEras[^1]); |
| 212 | 176 | | var oldPrioritizedItem = treeNode.PrioritizedItem; |
| 212 | 177 | | treeNode.PrioritizedItem = newPrioritizedItem; |
| | 178 | |
|
| 212 | 179 | | RaiseItemPriorityChanged(treeNode, oldPrioritizedItem); |
| | 180 | |
|
| 212 | 181 | | if (newPrioritizedItem.CompareTo(oldPrioritizedItem) >= 0) |
| 152 | 182 | | SiftUp(treeNode); |
| | 183 | | else |
| 60 | 184 | | SiftDown(treeNode); |
| | 185 | |
|
| 212 | 186 | | return oldPrioritizedItem; |
| 212 | 187 | | } |
| | 188 | |
|
| | 189 | | private void SiftUp(TreeNode<T> treeNode) |
| 152 | 190 | | { |
| 152 | 191 | | var ancestor = treeNode; |
| 218 | 192 | | while ( |
| 218 | 193 | | ancestor.Parent is TreeNode<T> ancestorParent && |
| 218 | 194 | | ancestorParent.PrioritizedItem.CompareTo(ancestor.PrioritizedItem) < 0) |
| 66 | 195 | | { |
| 66 | 196 | | (ancestor.PrioritizedItem, ancestorParent.PrioritizedItem) = |
| 66 | 197 | | (ancestorParent.PrioritizedItem, ancestor.PrioritizedItem); |
| 66 | 198 | | RaiseItemsSwapped(ancestor, ancestorParent); |
| | 199 | |
|
| 66 | 200 | | ancestor = ancestorParent; |
| 66 | 201 | | } |
| | 202 | |
|
| | 203 | | // If we are in SiftUp, it means that the priority has increased. The SiftUp may have bubbled up the increase |
| | 204 | | // up to the root of the tree. In such a case we need to check whether the new root has higher priority than |
| | 205 | | // the current max priority root, and update it if necessary. |
| 152 | 206 | | if (ancestor.RootsListNode != null) |
| 139 | 207 | | UpdateMaxRootsListNodeAfterRootNewOrIncrease(ancestor.RootsListNode); |
| 152 | 208 | | } |
| | 209 | |
|
| | 210 | | private void SiftDown(TreeNode<T> treeNode) |
| 60 | 211 | | { |
| 60 | 212 | | var descendant = treeNode; |
| 101 | 213 | | while ( |
| 141 | 214 | | descendant.Children.MaxBy(c => c.PrioritizedItem) is TreeNode<T> descendantMaxChild && |
| 101 | 215 | | descendantMaxChild.PrioritizedItem.CompareTo(descendant.PrioritizedItem) > 0) |
| 41 | 216 | | { |
| 41 | 217 | | (descendant.PrioritizedItem, descendantMaxChild.PrioritizedItem) = |
| 41 | 218 | | (descendantMaxChild.PrioritizedItem, descendant.PrioritizedItem); |
| 41 | 219 | | RaiseItemsSwapped(descendant, descendantMaxChild); |
| | 220 | |
|
| 41 | 221 | | descendant = descendantMaxChild; |
| 41 | 222 | | } |
| | 223 | |
|
| | 224 | | // If we are in SiftDown, it means that the priority has decreased. If the treeNode was a root, the SiftDown |
| | 225 | | // may have moved the root down the tree, and promoted one of its child to being the root. And even if it |
| | 226 | | // didn't happen, the root has now lower priority. So, if the max priority root was pointing to the root of |
| | 227 | | // this tree, we need to update the current max priority root, performing a linear scan of the roots. |
| 60 | 228 | | if (ReferenceEquals(MaxRootsListNode!.Value, treeNode)) |
| 15 | 229 | | UpdateMaxRootsListNode(); |
| 60 | 230 | | } |
| | 231 | |
|
| | 232 | | #endregion |
| | 233 | | } |