/// <summary> /// Adds an object to the priority queue. /// </summary> internal void Push(T value, double priority) { // Increase the size of the array if necessary. if (_count == _heap.Length) { Array.Resize <PriorityQueueItem <T> >(ref _heap, _count * 2); } PriorityQueueItem <T> queueItem = new PriorityQueueItem <T>(value, priority); // A common usage is to Push N items, then Pop them. Optimize for that // case by treating Push as a simple append until the first Top or Pop, // which establishes the heap property. After that, Push needs // to maintain the heap property. if (_isHeap) { SiftUp(_count, ref queueItem, 0); } else { _heap[_count] = queueItem; } _count++; }
/// <summary> /// Removes the first node (i.e., the logical root) from the heap. /// </summary> internal void Pop() { Debug.Assert(_count != 0); if (!_isHeap) { Heapify(); } if (_count > 0) { --_count; // discarding the root creates a gap at position 0. We fill the // gap with the item x from the last position, after first sifting // the gap to a position where inserting x will maintain the // heap property. This is done in two phases - SiftDown and SiftUp. // // The one-phase method found in many textbooks does 2 comparisons // per level, while this method does only 1. The one-phase method // examines fewer levels than the two-phase method, but it does // more comparisons unless x ends up in the top 2/3 of the tree. // That accounts for only n^(2/3) items, and x is even more likely // to end up near the bottom since it came from the bottom in the // first place. Overall, the two-phase method is noticeably better. PriorityQueueItem <T> x = _heap[_count]; // lift item x out from the last position int index = SiftDown(0); // sift the gap at the root down to the bottom SiftUp(index, ref x, 0); // sift the gap up, and insert x in its rightful position _heap[_count] = default(PriorityQueueItem <T>); // don't leak x } }
// Establish the heap property: _heap[k] >= _heap[HeapParent(k)], for 0<k<_count // Do this "bottom up", by iterating backwards. At each iteration, the // property inductively holds for k >= HeapLeftChild(i)+2; the body of // the loop extends the property to the children of position i (namely // k=HLC(i) and k=HLC(i)+1) by lifting item x out from position i, sifting // the resulting gap down to the bottom, then sifting it back up (within // the subtree under i) until finding x's rightful position. // // Iteration i does work proportional to the height (distance to leaf) // of the node at position i. Half the nodes are leaves with height 0; // there's nothing to do for these nodes, so we skip them by initializing // i to the last non-leaf position. A quarter of the nodes have height 1, // an eigth have height 2, etc. so the total work is ~ 1*n/4 + 2*n/8 + // 3*n/16 + ... = O(n). This is much cheaper than maintaining the // heap incrementally during the "Push" phase, which would cost O(n*log n). private void Heapify() { if (!_isHeap) { for (int i = _count / 2 - 1; i >= 0; --i) { // we use a two-phase method for the same reason Pop does PriorityQueueItem <T> x = _heap[i]; int index = SiftDown(i); SiftUp(index, ref x, i); } _isHeap = true; } }
// sift a gap at index up until it reaches the correct position for x, // or reaches the given boundary. Place x in the resulting position. private void SiftUp(int index, ref PriorityQueueItem <T> x, int boundary) { while (index > boundary) { int parent = HeapParent(index); if (_comparer.Compare(_heap[parent], x) > 0) { _heap[index] = _heap[parent]; index = parent; } else { break; } } _heap[index] = x; }
// // The _heap array represents a binary tree with the "shape" property. // If we number the nodes of a binary tree from left-to-right and top- // to-bottom as shown, // // 0 // / \ // / \ // 1 2 // / \ / \ // 3 4 5 6 // /\ / // 7 8 9 // // The shape property means that there are no gaps in the sequence of // numbered nodes, i.e., for all N > 0, if node N exists then node N-1 // also exists. For example, the next node added to the above tree would // be node 10, the right child of node 4. // // Because of this constraint, we can easily represent the "tree" as an // array, where node number == array index, and parent/child relationships // can be calculated instead of maintained explicitly. For example, for // any node N > 0, the parent of N is at array index (N - 1) / 2. // // In addition to the above, the first _count members of the _heap array // compose a "heap", meaning each child node is greater than or equal to // its parent node; thus, the root node is always the minimum (i.e., the // best match for the specified style, weight, and stretch) of the nodes // in the heap. // // Initially _count < 0, which means we have not yet constructed the heap. // On the first call to MoveNext, we construct the heap by "pushing" all // the nodes into it. Each successive call "pops" a node off the heap // until the heap is empty (_count == 0), at which time we've reached the // end of the sequence. // #region constructors internal PriorityQueue(int capacity = 0) { _heap = new PriorityQueueItem <T> [capacity > 0 ? capacity: DefaultCapacity]; _count = 0; _comparer = new PriorityQueueComparer <T>(); }
public int Compare(PriorityQueueItem <T> x, PriorityQueueItem <T> y) { return(x.Priority.CompareTo(y.Priority)); }