using System; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Priority_Queue { /// /// A copy of StablePriorityQueue which also has generic priority-type /// /// The values in the queue. Must extend the GenericPriorityQueueNode class /// The priority-type. Must extend IComparable<TPriority> public sealed class GenericPriorityQueue : IFixedSizePriorityQueue where TItem : GenericPriorityQueueNode where TPriority : IComparable { private int _numNodes; private TItem[] _nodes; private long _numNodesEverEnqueued; private readonly Comparison _comparer; /// /// Instantiate a new Priority Queue /// /// The max nodes ever allowed to be enqueued (going over this will cause undefined behavior) public GenericPriorityQueue(int maxNodes) : this(maxNodes, Comparer.Default) { } /// /// Instantiate a new Priority Queue /// /// The max nodes ever allowed to be enqueued (going over this will cause undefined behavior) /// The comparer used to compare TPriority values. public GenericPriorityQueue(int maxNodes, IComparer comparer) : this(maxNodes, comparer.Compare) { } /// /// Instantiate a new Priority Queue /// /// The max nodes ever allowed to be enqueued (going over this will cause undefined behavior) /// The comparison function to use to compare TPriority values public GenericPriorityQueue(int maxNodes, Comparison comparer) { #if DEBUG if (maxNodes <= 0) { throw new InvalidOperationException("New queue size cannot be smaller than 1"); } #endif _numNodes = 0; _nodes = new TItem[maxNodes + 1]; _numNodesEverEnqueued = 0; _comparer = comparer; } /// /// Returns the number of nodes in the queue. /// O(1) /// public int Count { get { return _numNodes; } } /// /// Returns the maximum number of items that can be enqueued at once in this queue. Once you hit this number (ie. once Count == MaxSize), /// attempting to enqueue another item will cause undefined behavior. O(1) /// public int MaxSize { get { return _nodes.Length - 1; } } /// /// Removes every node from the queue. /// O(n) (So, don't do this often!) /// #if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public void Clear() { Array.Clear(_nodes, 1, _numNodes); _numNodes = 0; } /// /// Returns (in O(1)!) whether the given node is in the queue. /// If node is or has been previously added to another queue, the result is undefined unless oldQueue.ResetNode(node) has been called /// O(1) /// #if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public bool Contains(TItem node) { #if DEBUG if(node == null) { throw new ArgumentNullException("node"); } if (node.Queue != null && !Equals(node.Queue)) { throw new InvalidOperationException("node.Contains was called on a node from another queue. Please call originalQueue.ResetNode() first"); } if (node.QueueIndex < 0 || node.QueueIndex >= _nodes.Length) { throw new InvalidOperationException("node.QueueIndex has been corrupted. Did you change it manually?"); } #endif return (_nodes[node.QueueIndex] == node); } /// /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out. /// If the queue is full, the result is undefined. /// If the node is already enqueued, the result is undefined. /// If node is or has been previously added to another queue, the result is undefined unless oldQueue.ResetNode(node) has been called /// O(log n) /// #if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public void Enqueue(TItem node, TPriority priority) { #if DEBUG if(node == null) { throw new ArgumentNullException("node"); } if(_numNodes >= _nodes.Length - 1) { throw new InvalidOperationException("Queue is full - node cannot be added: " + node); } if (node.Queue != null && !Equals(node.Queue)) { throw new InvalidOperationException("node.Enqueue was called on a node from another queue. Please call originalQueue.ResetNode() first"); } if (Contains(node)) { throw new InvalidOperationException("Node is already enqueued: " + node); } node.Queue = this; #endif node.Priority = priority; _numNodes++; _nodes[_numNodes] = node; node.QueueIndex = _numNodes; node.InsertionIndex = _numNodesEverEnqueued++; CascadeUp(node); } #if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif private void CascadeUp(TItem node) { //aka Heapify-up int parent; if (node.QueueIndex > 1) { parent = node.QueueIndex >> 1; TItem parentNode = _nodes[parent]; if(HasHigherPriority(parentNode, node)) return; //Node has lower priority value, so move parent down the heap to make room _nodes[node.QueueIndex] = parentNode; parentNode.QueueIndex = node.QueueIndex; node.QueueIndex = parent; } else { return; } while(parent > 1) { parent >>= 1; TItem parentNode = _nodes[parent]; if(HasHigherPriority(parentNode, node)) break; //Node has lower priority value, so move parent down the heap to make room _nodes[node.QueueIndex] = parentNode; parentNode.QueueIndex = node.QueueIndex; node.QueueIndex = parent; } _nodes[node.QueueIndex] = node; } #if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif private void CascadeDown(TItem node) { //aka Heapify-down int finalQueueIndex = node.QueueIndex; int childLeftIndex = 2 * finalQueueIndex; // If leaf node, we're done if(childLeftIndex > _numNodes) { return; } // Check if the left-child is higher-priority than the current node int childRightIndex = childLeftIndex + 1; TItem childLeft = _nodes[childLeftIndex]; if(HasHigherPriority(childLeft, node)) { // Check if there is a right child. If not, swap and finish. if(childRightIndex > _numNodes) { node.QueueIndex = childLeftIndex; childLeft.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childLeft; _nodes[childLeftIndex] = node; return; } // Check if the left-child is higher-priority than the right-child TItem childRight = _nodes[childRightIndex]; if(HasHigherPriority(childLeft, childRight)) { // left is highest, move it up and continue childLeft.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childLeft; finalQueueIndex = childLeftIndex; } else { // right is even higher, move it up and continue childRight.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childRight; finalQueueIndex = childRightIndex; } } // Not swapping with left-child, does right-child exist? else if(childRightIndex > _numNodes) { return; } else { // Check if the right-child is higher-priority than the current node TItem childRight = _nodes[childRightIndex]; if(HasHigherPriority(childRight, node)) { childRight.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childRight; finalQueueIndex = childRightIndex; } // Neither child is higher-priority than current, so finish and stop. else { return; } } while(true) { childLeftIndex = 2 * finalQueueIndex; // If leaf node, we're done if(childLeftIndex > _numNodes) { node.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = node; break; } // Check if the left-child is higher-priority than the current node childRightIndex = childLeftIndex + 1; childLeft = _nodes[childLeftIndex]; if(HasHigherPriority(childLeft, node)) { // Check if there is a right child. If not, swap and finish. if(childRightIndex > _numNodes) { node.QueueIndex = childLeftIndex; childLeft.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childLeft; _nodes[childLeftIndex] = node; break; } // Check if the left-child is higher-priority than the right-child TItem childRight = _nodes[childRightIndex]; if(HasHigherPriority(childLeft, childRight)) { // left is highest, move it up and continue childLeft.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childLeft; finalQueueIndex = childLeftIndex; } else { // right is even higher, move it up and continue childRight.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childRight; finalQueueIndex = childRightIndex; } } // Not swapping with left-child, does right-child exist? else if(childRightIndex > _numNodes) { node.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = node; break; } else { // Check if the right-child is higher-priority than the current node TItem childRight = _nodes[childRightIndex]; if(HasHigherPriority(childRight, node)) { childRight.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = childRight; finalQueueIndex = childRightIndex; } // Neither child is higher-priority than current, so finish and stop. else { node.QueueIndex = finalQueueIndex; _nodes[finalQueueIndex] = node; break; } } } } /// /// Returns true if 'higher' has higher priority than 'lower', false otherwise. /// Note that calling HasHigherPriority(node, node) (ie. both arguments the same node) will return false /// #if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif private bool HasHigherPriority(TItem higher, TItem lower) { var cmp = _comparer(higher.Priority, lower.Priority); return (cmp < 0 || (cmp == 0 && higher.InsertionIndex < lower.InsertionIndex)); } /// /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it. /// If queue is empty, result is undefined /// O(log n) /// #if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public TItem Dequeue() { #if DEBUG if(_numNodes <= 0) { throw new InvalidOperationException("Cannot call Dequeue() on an empty queue"); } if(!IsValidQueue()) { throw new InvalidOperationException("Queue has been corrupted (Did you update a node priority manually instead of calling UpdatePriority()?" + "Or add the same node to two different queues?)"); } #endif TItem returnMe = _nodes[1]; //If the node is already the last node, we can remove it immediately if(_numNodes == 1) { _nodes[1] = null; _numNodes = 0; return returnMe; } //Swap the node with the last node TItem formerLastNode = _nodes[_numNodes]; _nodes[1] = formerLastNode; formerLastNode.QueueIndex = 1; _nodes[_numNodes] = null; _numNodes--; //Now bubble formerLastNode (which is no longer the last node) down CascadeDown(formerLastNode); return returnMe; } /// /// Resize the queue so it can accept more nodes. All currently enqueued nodes are remain. /// Attempting to decrease the queue size to a size too small to hold the existing nodes results in undefined behavior /// O(n) /// public void Resize(int maxNodes) { #if DEBUG if (maxNodes <= 0) { throw new InvalidOperationException("Queue size cannot be smaller than 1"); } if (maxNodes < _numNodes) { throw new InvalidOperationException("Called Resize(" + maxNodes + "), but current queue contains " + _numNodes + " nodes"); } #endif TItem[] newArray = new TItem[maxNodes + 1]; int highestIndexToCopy = Math.Min(maxNodes, _numNodes); Array.Copy(_nodes, newArray, highestIndexToCopy + 1); _nodes = newArray; } /// /// Returns the head of the queue, without removing it (use Dequeue() for that). /// If the queue is empty, behavior is undefined. /// O(1) /// public TItem First { get { #if DEBUG if(_numNodes <= 0) { throw new InvalidOperationException("Cannot call .First on an empty queue"); } #endif return _nodes[1]; } } /// /// This method must be called on a node every time its priority changes while it is in the queue. /// Forgetting to call this method will result in a corrupted queue! /// Calling this method on a node not in the queue results in undefined behavior /// O(log n) /// #if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public void UpdatePriority(TItem node, TPriority priority) { #if DEBUG if(node == null) { throw new ArgumentNullException("node"); } if (node.Queue != null && !Equals(node.Queue)) { throw new InvalidOperationException("node.UpdatePriority was called on a node from another queue"); } if (!Contains(node)) { throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + node); } #endif node.Priority = priority; OnNodeUpdated(node); } #if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif private void OnNodeUpdated(TItem node) { //Bubble the updated node up or down as appropriate int parentIndex = node.QueueIndex >> 1; if(parentIndex > 0 && HasHigherPriority(node, _nodes[parentIndex])) { CascadeUp(node); } else { //Note that CascadeDown will be called if parentNode == node (that is, node is the root) CascadeDown(node); } } /// /// Removes a node from the queue. The node does not need to be the head of the queue. /// If the node is not in the queue, the result is undefined. If unsure, check Contains() first /// O(log n) /// #if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public void Remove(TItem node) { #if DEBUG if(node == null) { throw new ArgumentNullException("node"); } if (node.Queue != null && !Equals(node.Queue)) { throw new InvalidOperationException("node.Remove was called on a node from another queue"); } if (!Contains(node)) { throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + node); } #endif //If the node is already the last node, we can remove it immediately if(node.QueueIndex == _numNodes) { _nodes[_numNodes] = null; _numNodes--; return; } //Swap the node with the last node TItem formerLastNode = _nodes[_numNodes]; _nodes[node.QueueIndex] = formerLastNode; formerLastNode.QueueIndex = node.QueueIndex; _nodes[_numNodes] = null; _numNodes--; //Now bubble formerLastNode (which is no longer the last node) up or down as appropriate OnNodeUpdated(formerLastNode); } /// /// By default, nodes that have been previously added to one queue cannot be added to another queue. /// If you need to do this, please call originalQueue.ResetNode(node) before attempting to add it in the new queue /// #if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public void ResetNode(TItem node) { #if DEBUG if (node == null) { throw new ArgumentNullException("node"); } if (node.Queue != null && !Equals(node.Queue)) { throw new InvalidOperationException("node.ResetNode was called on a node from another queue"); } if (Contains(node)) { throw new InvalidOperationException("node.ResetNode was called on a node that is still in the queue"); } node.Queue = null; #endif node.QueueIndex = 0; } public IEnumerator GetEnumerator() { #if NET_VERSION_4_5 // ArraySegment does not implement IEnumerable before 4.5 IEnumerable e = new ArraySegment(_nodes, 1, _numNodes); return e.GetEnumerator(); #else for(int i = 1; i <= _numNodes; i++) yield return _nodes[i]; #endif } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Should not be called in production code. /// Checks to make sure the queue is still in a valid state. Used for testing/debugging the queue. /// public bool IsValidQueue() { for(int i = 1; i < _nodes.Length; i++) { if(_nodes[i] != null) { int childLeftIndex = 2 * i; if(childLeftIndex < _nodes.Length && _nodes[childLeftIndex] != null && HasHigherPriority(_nodes[childLeftIndex], _nodes[i])) return false; int childRightIndex = childLeftIndex + 1; if(childRightIndex < _nodes.Length && _nodes[childRightIndex] != null && HasHigherPriority(_nodes[childRightIndex], _nodes[i])) return false; } } return true; } } }