| | | 1 | | using MoreStructures.Utilities; |
| | | 2 | | using System.Collections; |
| | | 3 | | |
| | | 4 | | namespace MoreStructures.PriorityQueues.BinomialHeap; |
| | | 5 | | |
| | | 6 | | /// <summary> |
| | | 7 | | /// An <see cref="IPriorityQueue{T}"/> implementation based on a Binomial Max Heap of its items. It also supports |
| | | 8 | | /// <see cref="IMergeablePriorityQueue{T, TPQTarget}"/> operations. |
| | | 9 | | /// </summary> |
| | | 10 | | /// <typeparam name="T"><inheritdoc cref="IPriorityQueue{T}"/></typeparam> |
| | | 11 | | /// <remarks> |
| | | 12 | | /// <para id="definition"> |
| | | 13 | | /// DEFINITION |
| | | 14 | | /// <br/> |
| | | 15 | | /// - A <b>Binomial Heap</b> is a forest of <b>n-ary trees</b>, each respecting the <b>heap max property</b>, |
| | | 16 | | /// having a <b>binomial layout</b> and <b>unicity of degree</b>. |
| | | 17 | | /// <br/> |
| | | 18 | | /// - A tree respects the heap max property when each node has a value to be non-smaller than all its children |
| | | 19 | | /// (and, by transitivity, its grand-children, grand-grand-children etc.). |
| | | 20 | | /// <br/> |
| | | 21 | | /// - A tree has a binomial layout when is in the set of tree defined in the following constructive way: a |
| | | 22 | | /// singleton tree is a binomial tree of degree 0, a binomial tree of degree k + 1 is obtained merging two |
| | | 23 | | /// binomial trees of degree k, so that the root of one becomes immediate child of the root of the other. |
| | | 24 | | /// <br/> |
| | | 25 | | /// - A tree has unicity of degree when it is the only tree in the forest having its degree, which is the number of |
| | | 26 | | /// children it has. That means that there can be a single tree with 0 children (singleton), a single tree with |
| | | 27 | | /// 1 child, etc. |
| | | 28 | | /// </para> |
| | | 29 | | /// <para id="advantages"> |
| | | 30 | | /// ADVANTAGES AND DISADVANTAGES |
| | | 31 | | /// <br/> |
| | | 32 | | /// - This heap implementation is conceptually extending <see cref="BinaryHeap.BinaryHeapPriorityQueue{T}"/>, |
| | | 33 | | /// making heaps easily mergeable (i.e. in less time than O(n). |
| | | 34 | | /// <br/> |
| | | 35 | | /// - Binary Heaps provide logarithmic insertion and extraction. They can also provide linear construction, when |
| | | 36 | | /// the data is provided in batch and not online. |
| | | 37 | | /// <br/> |
| | | 38 | | /// - However, Binary Heap have O(n * log(n)) Time Complexity in merge. Merging the smaller heap into the bigger |
| | | 39 | | /// one, in the worst case the two heaps being merged have comparable size n / 2, resulting into an overall |
| | | 40 | | /// O(n / 2 * log(n / 2)) = O(n * log(n)) Time Complexity. |
| | | 41 | | /// <br/> |
| | | 42 | | /// - Merging the underlying arrays and building the new Binary Heap in batch would improve performance, yielding |
| | | 43 | | /// O(n) Time Complexity. Still an expensive operation, as it means going through all elements of one heap. |
| | | 44 | | /// <br/> |
| | | 45 | | /// - Binomial Heaps overcome this limitation and offer sub-linear performance by taking advantage of both the |
| | | 46 | | /// linked list layout and the tree layout, and taking the best of both worlds. |
| | | 47 | | /// <br/> |
| | | 48 | | /// - So the underlying idea behind Binomial Heaps is to combine linked lists and trees, and represent the data as |
| | | 49 | | /// a forest of n-ry heap trees (respecting the binomial layout), which can be easily merged together into a |
| | | 50 | | /// single Binomial Heap, due to their "recurrent" structure. |
| | | 51 | | /// <br/> |
| | | 52 | | /// - While <see cref="Push(T, int)"/> and <see cref="Pop"/> retain logarithmic complexity, merging also becomes |
| | | 53 | | /// a logarithmic operation. |
| | | 54 | | /// </para> |
| | | 55 | | /// </remarks> |
| | | 56 | | public partial class BinomialHeapPriorityQueue<T> |
| | | 57 | | : IMergeablePriorityQueue<T, BinomialHeapPriorityQueue<T>> |
| | | 58 | | where T : notnull |
| | | 59 | | { |
| | | 60 | | /// <summary> |
| | | 61 | | /// A <see cref="LinkedList{T}"/> of all the roots of the forest representing the heap. |
| | | 62 | | /// </summary> |
| | 313233 | 63 | | protected LinkedList<TreeNode<T>> Roots { get; } |
| | | 64 | | |
| | | 65 | | /// <summary> |
| | | 66 | | /// The total number of items in this queue. |
| | | 67 | | /// </summary> |
| | 179919 | 68 | | protected int ItemsCount { get; set; } |
| | | 69 | | |
| | | 70 | | /// <summary> |
| | | 71 | | /// Reference to the tree root in the forest with the highest priority. Makes Peek O(1). |
| | | 72 | | /// </summary> |
| | 180074 | 73 | | protected LinkedListNode<TreeNode<T>>? MaxRootsListNode { get; set; } |
| | | 74 | | |
| | | 75 | | |
| | | 76 | | /// <summary> |
| | | 77 | | /// A non-negative, zero-based, monotonically strictly increasing counter, incremented at every insertion into this |
| | | 78 | | /// data structure by a <see cref="Push(T, int)"/>. |
| | | 79 | | /// </summary> |
| | 78837 | 80 | | protected int CurrentPushTimestamp { get; set; } |
| | | 81 | | |
| | | 82 | | /// <summary> |
| | | 83 | | /// The eras in which all push timestamps created by this instance (e.g. on push), or merged into this instance, |
| | | 84 | | /// leave in. The last in the list is the current one. |
| | | 85 | | /// </summary> |
| | | 86 | | /// <remarks> |
| | | 87 | | /// By default, initialized to a singleton list containing the era "0". |
| | | 88 | | /// <br/> |
| | | 89 | | /// Depending on the implementation, may be relevant in merging. |
| | | 90 | | /// </remarks> |
| | 35702 | 91 | | protected IList<PushTimestampEra> PushTimestampEras { get; } |
| | | 92 | | |
| | | 93 | | |
| | | 94 | | /// <summary> |
| | | 95 | | /// Builds an empty priority queue. |
| | | 96 | | /// </summary> |
| | 13849 | 97 | | public BinomialHeapPriorityQueue() |
| | 13849 | 98 | | { |
| | 13849 | 99 | | Roots = new(); |
| | 13849 | 100 | | ItemsCount = 0; |
| | 13849 | 101 | | MaxRootsListNode = null; |
| | | 102 | | |
| | 13849 | 103 | | CurrentPushTimestamp = 0; |
| | 13849 | 104 | | PushTimestampEras = new List<PushTimestampEra>() { new(0) }; |
| | 13849 | 105 | | } |
| | | 106 | | |
| | | 107 | | /// <summary> |
| | | 108 | | /// Builds a deep, separate copy of the provided <paramref name="source"/> priority queue. |
| | | 109 | | /// </summary> |
| | | 110 | | /// <param name="source">The priority queue to be copied over.</param> |
| | | 111 | | /// <remarks> |
| | | 112 | | /// Doesn't copy the items themselves, it only deep-copies the internal structure of the <paramref name="source"/> |
| | | 113 | | /// queue. |
| | | 114 | | /// <br/> |
| | | 115 | | /// Warning: push timestamp eras are shared between items of the two queues! To be used only for |
| | | 116 | | /// <see cref="GetEnumerator"/> support. |
| | | 117 | | /// </remarks> |
| | 474 | 118 | | protected BinomialHeapPriorityQueue(BinomialHeapPriorityQueue<T> source) |
| | 474 | 119 | | { |
| | 1753 | 120 | | Roots = new LinkedList<TreeNode<T>>(source.Roots.Select(r => r.DeepCopy())); |
| | 3980 | 121 | | foreach (var rootsListNode in Roots.AsNodes()) |
| | 1279 | 122 | | rootsListNode.Value.RootsListNode = rootsListNode; |
| | | 123 | | |
| | 474 | 124 | | ItemsCount = source.ItemsCount; |
| | 474 | 125 | | UpdateMaxRootsListNode(); |
| | 474 | 126 | | CurrentPushTimestamp = source.CurrentPushTimestamp; |
| | 1772 | 127 | | PushTimestampEras = source.PushTimestampEras.Select(e => e with { }).ToList(); |
| | 474 | 128 | | } |
| | | 129 | | |
| | | 130 | | #region Public API |
| | | 131 | | |
| | | 132 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | | 133 | | /// <remarks> |
| | | 134 | | /// Checks the count internally stored, keeping track of the sum of the size of all trees in the linked list. |
| | | 135 | | /// <br/> |
| | | 136 | | /// Time and Space Complexity are O(1). |
| | | 137 | | /// </remarks> |
| | 19908 | 138 | | public virtual int Count => ItemsCount; |
| | | 139 | | |
| | | 140 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | | 141 | | /// <remarks> |
| | | 142 | | /// In order to return items in heap order (i.e. the max at each step), it first copies this priority queue into a |
| | | 143 | | /// second temporary queue, which can be mutated without affecting the state of this queue. |
| | | 144 | | /// <br/> |
| | | 145 | | /// It then iterates over the copy, calling <see cref="Pop"/> until it becomes empty. |
| | | 146 | | /// <br/> |
| | | 147 | | /// Time Complexity is O(n * log(n)) (when fully enumerated), because a single <see cref="Pop"/> on a Binomial |
| | | 148 | | /// Heap takes logarithmic time, and there are n items to be extracted. |
| | | 149 | | /// <br/> |
| | | 150 | | /// Space Complexity is O(n), as a copy of this queue is required as auxiliary data structure to emit elements in |
| | | 151 | | /// the right order of priority. |
| | | 152 | | /// </remarks> |
| | | 153 | | public virtual IEnumerator<T> GetEnumerator() |
| | 237 | 154 | | { |
| | 237 | 155 | | var copy = new BinomialHeapPriorityQueue<T>(this); |
| | 5962 | 156 | | while (copy.Count > 0) |
| | 5733 | 157 | | yield return copy.Pop().Item; |
| | 229 | 158 | | } |
| | | 159 | | |
| | | 160 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | | 161 | | /// <remarks> |
| | | 162 | | /// <inheritdoc cref="GetEnumerator"/> |
| | | 163 | | /// </remarks> |
| | 4 | 164 | | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
| | | 165 | | |
| | | 166 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | | 167 | | /// <remarks> |
| | | 168 | | /// Peeks the item of the linked list pointed by the "max root" internal property. |
| | | 169 | | /// <br/> |
| | | 170 | | /// By transitivity, the item of the max root contains the max item in the queue, since all roots are non-smaller |
| | | 171 | | /// than their descendants. |
| | | 172 | | /// <br/> |
| | | 173 | | /// Therefore, Time and Space Complexity are O(1). |
| | | 174 | | /// </remarks> |
| | | 175 | | public virtual PrioritizedItem<T> Peek() |
| | 1800 | 176 | | { |
| | 1800 | 177 | | if (MaxRootsListNode == null) |
| | 12 | 178 | | throw new InvalidOperationException($"Can't {nameof(Peek)} on an empty queue."); |
| | | 179 | | |
| | 1788 | 180 | | return MaxRootsListNode.Value.PrioritizedItem; |
| | 1788 | 181 | | } |
| | | 182 | | |
| | | 183 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | | 184 | | /// <remarks> |
| | | 185 | | /// <para id="algorithm"> |
| | | 186 | | /// ALGORITHM |
| | | 187 | | /// <br/> |
| | | 188 | | /// - Adds a new singleton tree T (degree 0) to the forest, wrapping the provided <paramref name="item"/> with |
| | | 189 | | /// its <paramref name="priority"/> into an object R with no children, and wrapping R into a |
| | | 190 | | /// <see cref="LinkedListNode{T}"/> LLN, which represents the root of T. |
| | | 191 | | /// <br/> |
| | | 192 | | /// - Then all trees with the same degree are merged together, with the same procedure explained in the |
| | | 193 | | /// documentation of <see cref="Pop"/>. |
| | | 194 | | /// <br/> |
| | | 195 | | /// - That ensures that the binomial shape of the forest (i.e. binomial trees all of different degrees) is |
| | | 196 | | /// preserved. Without merging, if a tree of degree 0 (i.e. a singleton tree) was already present before the |
| | | 197 | | /// push of the new root, the binomial heap property would have been violated. |
| | | 198 | | /// <br/> |
| | | 199 | | /// - After all equi-degree trees have been merged, a new linear scan of the root is done, to update the |
| | | 200 | | /// reference to the root with highest priority (so that <see cref="Peek"/> will work correctly and in |
| | | 201 | | /// constant time). |
| | | 202 | | /// </para> |
| | | 203 | | /// <para id="complexity"> |
| | | 204 | | /// COMPLEXITY |
| | | 205 | | /// <br/> |
| | | 206 | | /// - Adding a new root to the forest of trees is a constant-time operation, since the root is added at |
| | | 207 | | /// the end of the linked list representing the forest, which keeps references to both ends of the chain of |
| | | 208 | | /// nodes. |
| | | 209 | | /// <br/> |
| | | 210 | | /// - Merging equi-degree trees, to restore the binomial shape of the heap forest, and updating the max root |
| | | 211 | | /// reference are both logarithmic operations in time. The merge also requires a space proportional to the |
| | | 212 | | /// logarithm of the number of items in the heap, to instantiate its data structure. |
| | | 213 | | /// <br/> |
| | | 214 | | /// - Check the complexity analysis of <see cref="Pop"/> for further details. |
| | | 215 | | /// <br/> |
| | | 216 | | /// - Therefore, Time and Space Complexity are both O(log(n)). |
| | | 217 | | /// </para> |
| | | 218 | | /// </remarks> |
| | | 219 | | public virtual void Push(T item, int priority) |
| | 17462 | 220 | | { |
| | 17462 | 221 | | var prioritizedItem = new PrioritizedItem<T>(item, priority, CurrentPushTimestamp++, PushTimestampEras[^1]); |
| | 17462 | 222 | | var newRoot = new TreeNode<T> { PrioritizedItem = prioritizedItem }; |
| | 17462 | 223 | | AddRoot(newRoot); |
| | 17462 | 224 | | RaiseItemPushed(newRoot); |
| | 17462 | 225 | | MergeEquiDegreeTrees(); |
| | 17462 | 226 | | UpdateMaxRootsListNode(); |
| | 17462 | 227 | | } |
| | | 228 | | |
| | | 229 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | | 230 | | /// <remarks> |
| | | 231 | | /// <para id="algorithm"> |
| | | 232 | | /// ALGORITHM |
| | | 233 | | /// <br/> |
| | | 234 | | /// - The value of the tree root with highest priority is read, then the root is removed from the forest. |
| | | 235 | | /// <br/> |
| | | 236 | | /// - The orphan children of the removed root are promoted to being roots and added to the forest. |
| | | 237 | | /// <br/> |
| | | 238 | | /// - Then all trees with the same degree are merged together, where the root with lower priority becomes |
| | | 239 | | /// immediate child of the root with higher priority. |
| | | 240 | | /// <br/> |
| | | 241 | | /// - Merging is done efficiently by linearly scanning all the tree roots in the forest, and keeping an array |
| | | 242 | | /// A of tree roots indexed by their degree. |
| | | 243 | | /// <br/> |
| | | 244 | | /// - Every time a second tree with the same degree is encountered: if T1 has degree k and A[k] already |
| | | 245 | | /// references another tree T2 with degree k, T1 and T2 are merged into a tree whose root has degree k + 1, |
| | | 246 | | /// A[k] is reset and A[k + 1] is set. |
| | | 247 | | /// <br/> |
| | | 248 | | /// - After all equi-degree trees have been merged, a new linear scan of the root is done, to update the |
| | | 249 | | /// reference to the root with highest priority. |
| | | 250 | | /// </para> |
| | | 251 | | /// <para> |
| | | 252 | | /// COMPLEXITY |
| | | 253 | | /// <br/> |
| | | 254 | | /// - Removing the root with highest priority from the forest is a O(1) operation. |
| | | 255 | | /// <br/> |
| | | 256 | | /// - Promoting to roots all its children is proportional to the number of children, a root of the forest has. |
| | | 257 | | /// <br/> |
| | | 258 | | /// - In a Binomial Heap the max degree of the roots of the forest is logarithmic with the total number of |
| | | 259 | | /// items in the heap. Therefore, promotion of children to roots is a O(log(n)) operation. |
| | | 260 | | /// <br/> |
| | | 261 | | /// - Merging equi-degree trees requires a full scan of the roots in the forest. If the forest were made of |
| | | 262 | | /// a lot of shallow trees, that would result into a O(n) operation. |
| | | 263 | | /// <br/> |
| | | 264 | | /// - However, while <see cref="Push(T, int)"/> increases every time by 1 the count of trees, after n |
| | | 265 | | /// insertions in O(1), a single O(n) scan done by a <see cref="Pop"/> would merge trees, making them |
| | | 266 | | /// logarithmic in number. |
| | | 267 | | /// <br/> |
| | | 268 | | /// - Updating the max root reference takes time proportional to the number of trees in the forest AFTER the |
| | | 269 | | /// merging. |
| | | 270 | | /// <br/> |
| | | 271 | | /// - Because the merging of equi-degree trees leaves a forest of trees all of different degrees, and the max |
| | | 272 | | /// degree is logarithmic with n, at the end of the merging procedure there can be at most a logarithmic |
| | | 273 | | /// number of different trees in the forest. |
| | | 274 | | /// <br/> |
| | | 275 | | /// - So the linear scan of tree roots in the forest to find the max root reference takes only a logarithmic |
| | | 276 | | /// amount of time. |
| | | 277 | | /// <br/> |
| | | 278 | | /// - Therefore, Time Complexity is O(log(n)). Space Complexity is also O(log(n)), since merging equi-degree |
| | | 279 | | /// trees requires instantiating an array index by degrees, and max degree is O(log(n)). |
| | | 280 | | /// </para> |
| | | 281 | | /// </remarks> |
| | | 282 | | public virtual PrioritizedItem<T> Pop() |
| | 19019 | 283 | | { |
| | 19019 | 284 | | if (MaxRootsListNode == null) |
| | 12 | 285 | | throw new InvalidOperationException($"Can't {nameof(Pop)} on an empty queue."); |
| | | 286 | | |
| | 19007 | 287 | | RaiseItemPopping(MaxRootsListNode.Value); |
| | 19007 | 288 | | var oldRoot = DetachFromRoots(MaxRootsListNode); |
| | 19007 | 289 | | ItemsCount--; |
| | | 290 | | |
| | 206035 | 291 | | foreach (var child in oldRoot.Children.ToList()) |
| | 74507 | 292 | | PromoteChildToRoot(child); |
| | | 293 | | |
| | 19007 | 294 | | MergeEquiDegreeTrees(); |
| | 19007 | 295 | | UpdateMaxRootsListNode(); |
| | | 296 | | |
| | 19007 | 297 | | return oldRoot.PrioritizedItem; |
| | 19007 | 298 | | } |
| | | 299 | | |
| | | 300 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | | 301 | | /// <remarks> |
| | | 302 | | /// <para id="algorithm"> |
| | | 303 | | /// ALGORITHM |
| | | 304 | | /// <br/> |
| | | 305 | | /// - Before the actual merge of roots, push timestamp eras need to be adjusted, to deal with potentially |
| | | 306 | | /// conflicting timestamps from the two queues without scanning through all items, which would take a linear |
| | | 307 | | /// amount of time. |
| | | 308 | | /// <br/> |
| | | 309 | | /// - In order to do so, the max push timestamp era M, between the current era of the source and the current |
| | | 310 | | /// era of the target (the last in the list of eras of each), is calculated. |
| | | 311 | | /// <br/> |
| | | 312 | | /// - Then, all the push timestamp eras of the target are <b>mutated</b> (i.e. by a set accessor on the |
| | | 313 | | /// instance) in order (from the first = the older, to the last = the most recent) to M + 1, M + 2, ... |
| | | 314 | | /// <br/> |
| | | 315 | | /// - This way all items of the target are considered as added after all current items of the source, and keep |
| | | 316 | | /// the order they had in the target between themselves, before the merge. |
| | | 317 | | /// <br/> |
| | | 318 | | /// - Then, a new current push timestamp era of the source is <b>added</b> (i.e. new instance) with a new |
| | | 319 | | /// push timestamp era set to M + N + 1, so that all items of the source coming after the merge are |
| | | 320 | | /// considered as added after all items of the target added during merge. |
| | | 321 | | /// <br/> |
| | | 322 | | /// - After that, the algorithm iterates over all the roots of the target heap. |
| | | 323 | | /// <br/> |
| | | 324 | | /// - Add each root R to the linked list of roots of the source heap and increases the total items count of |
| | | 325 | | /// the source heap by the number of items in R, which can be calculated without traversing, from the number |
| | | 326 | | /// of immediate children c of R as 2^c, being R a binomial tree. |
| | | 327 | | /// tree. |
| | | 328 | | /// <br/> |
| | | 329 | | /// - For each added root, <see cref=" RaiseRootMerged(TreeNode{T})"/> is invoked. |
| | | 330 | | /// <br/> |
| | | 331 | | /// - Then binomial heap shape is restored by merging together all trees with the same degree and a new linear |
| | | 332 | | /// scan of the root is done, to update the reference to the root with highest priority, exactly as in |
| | | 333 | | /// <see cref="Push(T, int)"/> and <see cref="Pop"/>. |
| | | 334 | | /// <br/> |
| | | 335 | | /// - To separate avoid interferences between this queue and the <paramref name="targetPriorityQueue"/>, the |
| | | 336 | | /// <paramref name="targetPriorityQueue"/> is cleared of all its items. |
| | | 337 | | /// <br/> |
| | | 338 | | /// - Its current push timestamp is left unchanged and the push timestamp eras are cleared and set to a new |
| | | 339 | | /// single instance with the same era value it had before the merge. This way all new items in the |
| | | 340 | | /// <paramref name="targetPriorityQueue"/> will share the reference to the new era object, and wont interfere |
| | | 341 | | /// with the era object of all items moved to this priority queue. |
| | | 342 | | /// </para> |
| | | 343 | | /// <para id="complexity"> |
| | | 344 | | /// COMPLEXITY |
| | | 345 | | /// <br/> |
| | | 346 | | /// - For this analysis, events, and in particular <see cref="RaiseRootMerged(TreeNode{T})"/>, are considered |
| | | 347 | | /// O(1) both in Time and Space Complexity. |
| | | 348 | | /// <br/> |
| | | 349 | | /// - The number of roots of the target heap is logarithmic with the number m of items in the target heap. |
| | | 350 | | /// <br/> |
| | | 351 | | /// - Adding each root R of the target heap to the forest of the source heap and increasing the items count are |
| | | 352 | | /// both constant-time operations. |
| | | 353 | | /// <br/> |
| | | 354 | | /// - Housekeeping operations, done after that on the source heap, take logarithmic time, as explained in |
| | | 355 | | /// <see cref="Pop"/>. |
| | | 356 | | /// <br/> |
| | | 357 | | /// - Clearing the target is also a constant-time operation. |
| | | 358 | | /// <br/> |
| | | 359 | | /// - Therefore, Time and Space Complexity are O(log(m)). |
| | | 360 | | /// </para> |
| | | 361 | | /// </remarks> |
| | | 362 | | public virtual void MergeFrom(BinomialHeapPriorityQueue<T> targetPriorityQueue) |
| | 468 | 363 | | { |
| | 468 | 364 | | var previousSourceEra = PushTimestampEras[^1].Era; |
| | 468 | 365 | | var previousTargetEra = targetPriorityQueue.PushTimestampEras[^1].Era; |
| | 468 | 366 | | var previousNextEra = Math.Max(previousSourceEra, previousTargetEra) + 1; |
| | | 367 | | |
| | 2356 | 368 | | foreach (var targetPushstampEra in targetPriorityQueue.PushTimestampEras) |
| | 476 | 369 | | { |
| | 476 | 370 | | targetPushstampEra.Era = previousNextEra++; |
| | 476 | 371 | | PushTimestampEras.Add(targetPushstampEra); |
| | 476 | 372 | | } |
| | | 373 | | |
| | 468 | 374 | | PushTimestampEras.Add(new PushTimestampEra(previousNextEra)); |
| | 468 | 375 | | CurrentPushTimestamp = 0; |
| | | 376 | | |
| | 7240 | 377 | | foreach (var targetRoot in targetPriorityQueue.Roots) |
| | 2918 | 378 | | { |
| | 2918 | 379 | | AttachToRoots(targetRoot); |
| | 2918 | 380 | | ItemsCount += (int)Math.Pow(2, targetRoot.Children.Count); |
| | 2918 | 381 | | RaiseRootMerged(targetRoot); |
| | 2918 | 382 | | } |
| | | 383 | | |
| | 468 | 384 | | MergeEquiDegreeTrees(); |
| | 468 | 385 | | UpdateMaxRootsListNode(); |
| | | 386 | | |
| | 468 | 387 | | targetPriorityQueue.PushTimestampEras.Clear(); |
| | 468 | 388 | | targetPriorityQueue.PushTimestampEras.Add(new PushTimestampEra(previousTargetEra)); |
| | 468 | 389 | | targetPriorityQueue.Clear(); |
| | 468 | 390 | | } |
| | | 391 | | |
| | | 392 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | | 393 | | /// <remarks> |
| | | 394 | | /// First, calls <see cref="RaiseItemsClearing"/>. |
| | | 395 | | /// <br/> |
| | | 396 | | /// Then, removes all the trees from the forest, reset to the items count to 0 and set the reference to the |
| | | 397 | | /// max priority root to <see langword="null"/>. |
| | | 398 | | /// <br/> |
| | | 399 | | /// The internal push timestamp counter is not reset, nor the era. Therefore, new pushes after the clear will |
| | | 400 | | /// receive push timestamps strictly higher than the ones assigned to the items in the queue before the clear. |
| | | 401 | | /// <br/> |
| | | 402 | | /// Time and Space Complexity are O(1), with <see cref="RaiseItemsClearing"/> assumed to be O(1). |
| | | 403 | | /// </remarks> |
| | | 404 | | public virtual void Clear() |
| | 684 | 405 | | { |
| | 684 | 406 | | RaiseItemsClearing(); |
| | | 407 | | |
| | 684 | 408 | | Roots.Clear(); |
| | | 409 | | |
| | 684 | 410 | | ItemsCount = 0; |
| | 684 | 411 | | MaxRootsListNode = null; |
| | 684 | 412 | | } |
| | | 413 | | |
| | | 414 | | #endregion |
| | | 415 | | |
| | | 416 | | #region Hooks |
| | | 417 | | |
| | | 418 | | /// <summary> |
| | | 419 | | /// Invoked just after an item has been pushed into the heap (as a root). |
| | | 420 | | /// </summary> |
| | | 421 | | /// <param name="newRoot">The <see cref="TreeNode{T}"/> added to the heap.</param> |
| | | 422 | | /// <remarks> |
| | | 423 | | /// Not invoked on merging, for which <see cref="RaiseRootMerged(TreeNode{T})"/> is invoked instead. |
| | | 424 | | /// </remarks> |
| | 39368 | 425 | | protected virtual void RaiseItemPushed(TreeNode<T> newRoot) { } |
| | | 426 | | |
| | | 427 | | /// <summary> |
| | | 428 | | /// Invoked just before an item is removed from the heap. |
| | | 429 | | /// </summary> |
| | | 430 | | /// <param name="root">The <see cref="TreeNode{T}"/> about to be removed from the heap.</param> |
| | 27526 | 431 | | protected virtual void RaiseItemPopping(TreeNode<T> root) { } |
| | | 432 | | |
| | | 433 | | /// <summary> |
| | | 434 | | /// Invoked just before the all the items in the heap are wiped out. |
| | | 435 | | /// </summary> |
| | 1368 | 436 | | protected virtual void RaiseItemsClearing() { } |
| | | 437 | | |
| | | 438 | | /// <summary> |
| | | 439 | | /// Invoked just after an heap tree has been added to the forest (root and all its descendants). |
| | | 440 | | /// </summary> |
| | | 441 | | /// <param name="root">The root <see cref="TreeNode{T}"/> of the heap tree added to the forest.</param> |
| | 5836 | 442 | | protected virtual void RaiseRootMerged(TreeNode<T> root) { } |
| | | 443 | | |
| | | 444 | | #endregion |
| | | 445 | | |
| | | 446 | | #region Helpers |
| | | 447 | | |
| | | 448 | | /// <summary> |
| | | 449 | | /// Updates the reference to the max priority root to the provided <paramref name="rootsListNode"/>, |
| | | 450 | | /// if that root has a higher priority than the value of the current max priority root. |
| | | 451 | | /// </summary> |
| | | 452 | | /// <param name="rootsListNode">The root whose priority has been increased.</param> |
| | | 453 | | protected void UpdateMaxRootsListNodeAfterRootNewOrIncrease(LinkedListNode<TreeNode<T>> rootsListNode) |
| | 31736 | 454 | | { |
| | 31736 | 455 | | if (MaxRootsListNode == null || |
| | 31736 | 456 | | MaxRootsListNode.Value.PrioritizedItem.CompareTo(rootsListNode.Value.PrioritizedItem) < 0) |
| | 18205 | 457 | | MaxRootsListNode = rootsListNode; |
| | 31736 | 458 | | } |
| | | 459 | | |
| | | 460 | | /// <summary> |
| | | 461 | | /// Performs a linear scan of the roots and update the <see cref="MaxRootsListNode"/> with a reference to the root |
| | | 462 | | /// of max priority. |
| | | 463 | | /// </summary> |
| | | 464 | | protected void UpdateMaxRootsListNode() |
| | 37486 | 465 | | { |
| | 37486 | 466 | | if (ItemsCount > 0) |
| | 35103 | 467 | | { |
| | 35103 | 468 | | LinkedListNode<TreeNode<T>> maxRootNode = Roots.First!; |
| | 268015 | 469 | | foreach (var rootNode in Roots.AsNodes().Skip(1)) |
| | 81353 | 470 | | { |
| | 81353 | 471 | | if (rootNode.Value.PrioritizedItem.CompareTo(maxRootNode.Value.PrioritizedItem) > 0) |
| | 16389 | 472 | | maxRootNode = rootNode; |
| | 81353 | 473 | | } |
| | | 474 | | |
| | 35103 | 475 | | MaxRootsListNode = maxRootNode; |
| | 35103 | 476 | | } |
| | | 477 | | else |
| | 2383 | 478 | | { |
| | 2383 | 479 | | MaxRootsListNode = null; |
| | 2383 | 480 | | } |
| | 37486 | 481 | | } |
| | | 482 | | |
| | | 483 | | /// <summary> |
| | | 484 | | /// Adds a brand new <see cref="TreeNode{T}"/> to the heap, as a new root in the forest. |
| | | 485 | | /// </summary> |
| | | 486 | | /// <param name="newRoot">The new root.</param> |
| | | 487 | | protected void AddRoot(TreeNode<T> newRoot) |
| | 31597 | 488 | | { |
| | 31597 | 489 | | var newRootsListNode = AttachToRoots(newRoot); |
| | 31597 | 490 | | ItemsCount++; |
| | 31597 | 491 | | UpdateMaxRootsListNodeAfterRootNewOrIncrease(newRootsListNode); |
| | 31597 | 492 | | } |
| | | 493 | | |
| | | 494 | | /// <summary> |
| | | 495 | | /// Attaches the provided <see cref="TreeNode{T}"/> to the <see cref="Roots"/>. |
| | | 496 | | /// </summary> |
| | | 497 | | /// <param name="newRoot">A node with no parent, and not already a root.</param> |
| | | 498 | | /// <returns> |
| | | 499 | | /// The <see cref="LinkedListNode{T}"/> of <see cref="Roots"/> pointing to the <paramref name="newRoot"/>. |
| | | 500 | | /// </returns> |
| | | 501 | | protected LinkedListNode<TreeNode<T>> AttachToRoots(TreeNode<T> newRoot) |
| | 109160 | 502 | | { |
| | 109160 | 503 | | var newRootsListNode = Roots.AddLast(newRoot); |
| | 109160 | 504 | | newRoot.RootsListNode = newRootsListNode; |
| | 109160 | 505 | | return newRootsListNode; |
| | 109160 | 506 | | } |
| | | 507 | | |
| | | 508 | | /// <summary> |
| | | 509 | | /// Merges all trees of the <see cref="Roots"/> forest with the same degree (number of children of the root). |
| | | 510 | | /// </summary> |
| | | 511 | | protected virtual void MergeEquiDegreeTrees() |
| | 36997 | 512 | | { |
| | 36997 | 513 | | var degrees = new Dictionary<int, LinkedListNode<TreeNode<T>>>(); |
| | 492823 | 514 | | foreach (var rootsListNode in Roots.AsNodes().ToList()) |
| | 190916 | 515 | | { |
| | 190916 | 516 | | var currentRootsListNode = rootsListNode; |
| | 190916 | 517 | | var currentRootsListNodeDegree = currentRootsListNode.Value.Children.Count; |
| | 266679 | 518 | | while (degrees.TryGetValue(currentRootsListNodeDegree, out var otherRoot)) |
| | 75763 | 519 | | { |
| | 75763 | 520 | | var newRootsListNode = MergeRoots(currentRootsListNode, otherRoot); |
| | 75763 | 521 | | degrees.Remove(currentRootsListNodeDegree); |
| | | 522 | | |
| | 75763 | 523 | | currentRootsListNodeDegree = newRootsListNode.Value.Children.Count; |
| | 75763 | 524 | | currentRootsListNode = newRootsListNode; |
| | 75763 | 525 | | } |
| | | 526 | | |
| | 190916 | 527 | | degrees[currentRootsListNodeDegree] = currentRootsListNode; |
| | 190916 | 528 | | } |
| | 36997 | 529 | | } |
| | | 530 | | |
| | | 531 | | /// <summary> |
| | | 532 | | /// Merges the two provided trees of the forest into a single one, preserving the heap property. |
| | | 533 | | /// </summary> |
| | | 534 | | /// <param name="first"> |
| | | 535 | | /// The <see cref="LinkedListNode{T}"/> of <see cref="Roots"/> pointing to the root of the first tree to merge. |
| | | 536 | | /// </param> |
| | | 537 | | /// <param name="second"> |
| | | 538 | | /// The <see cref="LinkedListNode{T}"/> of <see cref="Roots"/> pointing to the root of the second tree to merge. |
| | | 539 | | /// </param> |
| | | 540 | | /// <returns> |
| | | 541 | | /// The <see cref="LinkedListNode{T}"/> of <see cref="Roots"/> pointing to the root of the merged tree: either |
| | | 542 | | /// <paramref name="first"/> or <paramref name="second"/>, depending on the <see cref="PrioritizedItem{T}"/> stored |
| | | 543 | | /// in the <see cref="TreeNode{T}.PrioritizedItem"/> of <see cref="LinkedListNode{T}.Value"/>. |
| | | 544 | | /// </returns> |
| | | 545 | | protected LinkedListNode<TreeNode<T>> MergeRoots( |
| | | 546 | | LinkedListNode<TreeNode<T>> first, LinkedListNode<TreeNode<T>> second) |
| | 75763 | 547 | | { |
| | 75763 | 548 | | if (first.Value.PrioritizedItem.CompareTo(second.Value.PrioritizedItem) >= 0) |
| | 36242 | 549 | | { |
| | 36242 | 550 | | DetachFromRoots(second); |
| | 36242 | 551 | | first.Value.AddChild(second.Value); |
| | 36242 | 552 | | return first; |
| | | 553 | | } |
| | | 554 | | |
| | 39521 | 555 | | DetachFromRoots(first); |
| | 39521 | 556 | | second.Value.AddChild(first.Value); |
| | 39521 | 557 | | return second; |
| | 75763 | 558 | | } |
| | | 559 | | |
| | | 560 | | /// <summary> |
| | | 561 | | /// Detaches the <see cref="TreeNode{T}"/> pointed by the provided <paramref name="rootsListNode"/> from the |
| | | 562 | | /// <see cref="Roots"/>. |
| | | 563 | | /// </summary> |
| | | 564 | | /// <param name="rootsListNode"> |
| | | 565 | | /// The <see cref="LinkedListNode{T}"/> of <see cref="Roots"/>, pointing to the <see cref="TreeNode{T}"/> root. |
| | | 566 | | /// </param> |
| | | 567 | | /// <returns>The detached <see cref="TreeNode{T}"/> root.</returns> |
| | | 568 | | protected TreeNode<T> DetachFromRoots(LinkedListNode<TreeNode<T>> rootsListNode) |
| | 94770 | 569 | | { |
| | 94770 | 570 | | var oldRoot = rootsListNode.Value; |
| | 94770 | 571 | | oldRoot.RootsListNode = null; |
| | 94770 | 572 | | Roots.Remove(rootsListNode); |
| | | 573 | | |
| | 94770 | 574 | | return oldRoot; |
| | 94770 | 575 | | } |
| | | 576 | | |
| | | 577 | | /// <summary> |
| | | 578 | | /// Promotes the provided <see cref="TreeNode{T}"/>, to being a root, detaching it from |
| | | 579 | | /// its current parent. |
| | | 580 | | /// </summary> |
| | | 581 | | /// <param name="child">A child of a node of a tree in the forest.</param> |
| | | 582 | | protected virtual void PromoteChildToRoot(TreeNode<T> child) |
| | 74645 | 583 | | { |
| | 74645 | 584 | | child.DetachFromParent(); |
| | 74645 | 585 | | AttachToRoots(child); |
| | 74645 | 586 | | } |
| | | 587 | | |
| | | 588 | | #endregion |
| | | 589 | | } |