Priority queue
In computer science, a priority queue is an abstract data type similar to regular queue or stack data structure in which each element additionally has a "priority" associated with it. In a priority queue, an element with high priority is served before an element with low priority. In some implementations, if two elements have the same priority, they are served according to the order in which they were enqueued, while in other implementations, ordering of elements with the same priority is undefined.
While priority queues are often implemented with heaps, they are conceptually distinct from heaps. A priority queue is a concept like "a list" or "a map"; just as a list can be implemented with a linked list or an array, a priority queue can be implemented with a heap or a variety of other methods such as an unordered array.
Operations
A priority queue must at least support the following operations:- is_empty: check whether the queue has no elements.
- insert_with_priority: add an element to the queue with an associated priority.
- pull_highest_priority_element: remove the element from the queue that has the highest priority, and return it.
- : This is also known as "pop_element", "get_maximum_element" or "get_front_element".
- : Some conventions reverse the order of priorities, considering lower values to be higher priority, so this may also be known as "get_minimum_element", and is often referred to as "get-min" in the literature.
- : This may instead be specified as separate "peek_at_highest_priority_element" and "delete_element" functions, which can be combined to produce "pull_highest_priority_element".
More advanced implementations may support more complicated operations, such as pull_lowest_priority_element, inspecting the first few highest- or lowest-priority elements, clearing the queue, clearing subsets of the queue, performing a batch insert, merging two or more queues into one, incrementing priority of any element, etc.
One can imagine a priority queue as a modified queue, but when one would get the next element off the queue, the highest-priority element is retrieved first.
Stacks and queues may be modeled as particular kinds of priority queues. As a reminder, here is how stacks and queues behave:
- stack - elements are pulled in last-in first-out-order
- queue - elements are pulled in first-in first-out-order
Implementation
Naive implementations
There are a variety of simple, usually inefficient, ways to implement a priority queue. They provide an analogy to help one understand what a priority queue is. For instance, one can keep all the elements in an unsorted list. Whenever the highest-priority element is requested, search through all elements for the one with the highest priority. insertion time, OUsual implementation
To improve performance, priority queues typically use a heap as their backbone, giving O performance for inserts and removals, and O to build initially from a set of n elements. Variants of the basic heap data structure such as pairing heaps or Fibonacci heaps can provide better bounds for some operations.Alternatively, when a self-balancing binary search tree is used, insertion and removal also take O time, although building trees from existing sequences of elements takes O time; this is typical where one might already have access to these data structures, such as with third-party or standard libraries.
From a computational-complexity standpoint, priority queues are congruent to sorting algorithms. The section on the equivalence of priority queues and sorting algorithms, below, describes how efficient sorting algorithms can create efficient priority queues.
Specialized heaps
There are several specialized heap data structures that either supply additional operations or outperform heap-based implementations for specific types of keys, specifically integer keys. Suppose the set of possible keys is.- When only insert, find-min and extract-min are needed and in case of integer priorities, a bucket queue can be constructed as an array of linked lists plus a pointer, initially. Inserting an item with key appends the item to the 'th, and updates, both in constant time. Extract-min deletes and returns one item from the list with index, then increments if needed until it again points to a non-empty list; this takes time in the worst case. These queues are useful for sorting the vertices of a graph by their degree.
- A van Emde Boas tree supports the minimum, maximum, insert, delete, search, extract-min, extract-max, predecessor and successor operations in O time, but has a space cost for small queues of about O, where m is the number of bits in the priority value. The space can be reduced significantly with hashing.
- The Fusion tree by Fredman and Willard implements the minimum operation in O time and insert and extract-min operations in time. However it is stated by the author that, "Our algorithms have theoretical interest only; The constant factors involved in the execution times preclude practicality."
Monotone priority queues are specialized queues that are optimized for the case where no item is ever inserted that has a lower priority than any item previously extracted. This restriction is met by several practical applications of priority queues.
Summary of running times
Equivalence of priority queues and sorting algorithms
Using a priority queue to sort
The semantics of priority queues naturally suggest a sorting method: insert all the elements to be sorted into a priority queue, and sequentially remove them; they will come out in sorted order. This is actually the procedure used by several sorting algorithms, once the layer of abstraction provided by the priority queue is removed. This sorting method is equivalent to the following sorting algorithms:Name | Priority Queue Implementation | Best | Average | Worst |
Heapsort | Heap | |||
Smoothsort | Leonardo Heap | |||
Selection sort | Unordered Array | |||
Insertion sort | Ordered Array | |||
Tree sort | Self-balancing binary search tree |
Using a sorting algorithm to make a priority queue
A sorting algorithm can also be used to implement a priority queue. Specifically, Thorup says:
We present a general deterministic linear space reduction from priority queues to sorting implying that if we can sort up to n keys in S time per key, then there is a priority queue supporting delete and insert in O time and find-min in constant time.
That is, if there is a sorting algorithm which can sort in O time per key, where S is some function of n and word size, then one can use the given procedure to create a priority queue where pulling the highest-priority element is O time, and inserting new elements is O time. For example, if one has an O sort algorithm, one can create a priority queue with O pulling and O insertion.
Libraries
A priority queue is often considered to be a "container data structure".The Standard Template Library, and the C++ 1998 standard, specifies
priority_queue
as one of the STL container adaptor class templates. However, it does not specify how two elements with same priority should be served, and indeed, common implementations will not return them according to their order in the queue. It implements a max-priority-queue, and has three parameters: a comparison object for sorting such as a function object, the underlying container for storing the data structures, and two iterators to the beginning and end of a sequence. Unlike actual STL containers, it does not allow iteration of its elements. STL also has utility functions for manipulating another random-access container as a binary max-heap. The Boost libraries also have an implementation in the library heap.Python's module implements a binary min-heap on top of a list.
Java's library contains a class, which implements a min-priority-queue.
Scala's library contains a class, which implements a max-priority-queue.
Go's library contains a module, which implements a min-heap on top of any compatible data structure.
The Standard PHP Library extension contains the class .
Apple's Core Foundation framework contains a structure, which implements a min-heap.
Applications
Bandwidth management
Priority queuing can be used to manage limited resources such as bandwidth on a transmission line from a network router. In the event of outgoing traffic queuing due to insufficient bandwidth, all other queues can be halted to send the traffic from the highest priority queue upon arrival. This ensures that the prioritized traffic is forwarded with the least delay and the least likelihood of being rejected due to a queue reaching its maximum capacity. All other traffic can be handled when the highest priority queue is empty. Another approach used is to send disproportionately more traffic from higher priority queues.Many modern protocols for local area networks also include the concept of priority queues at the media access control sub-layer to ensure that high-priority applications experience lower latency than other applications which can be served with best effort service. Examples include IEEE 802.11e and ITU-T G.hn.
Usually a limitation is set to limit the bandwidth that traffic from the highest priority queue can take, in order to prevent high priority packets from choking off all other traffic. This limit is usually never reached due to high level control instances such as the Cisco Callmanager, which can be programmed to inhibit calls which would exceed the programmed bandwidth limit.
Discrete event simulation
Another use of a priority queue is to manage the events in a discrete event simulation. The events are added to the queue with their simulation time used as the priority. The execution of the simulation proceeds by repeatedly pulling the top of the queue and executing the event thereon.See also: Scheduling, queueing theory
Dijkstra's algorithm
When the graph is stored in the form of adjacency list or matrix, priority queue can be used to extract minimum efficiently when implementing Dijkstra's algorithm, although one also needs the ability to alter the priority of a particular vertex in the priority queue efficiently.Huffman coding
requires one to repeatedly obtain the two lowest-frequency trees. A priority queue is one method of doing this.Best-first search algorithms
algorithms, like the A* search algorithm, find the shortest path between two vertices or nodes of a weighted graph, trying out the most promising routes first. A priority queue is used to keep track of unexplored routes; the one for which the estimate of the total path length is smallest is given highest priority. If memory limitations make best-first search impractical, variants like the SMA* algorithm can be used instead, with a double-ended priority queue to allow removal of low-priority items.ROAM triangulation algorithm
The Real-time Optimally Adapting Meshes algorithm computes a dynamically changing triangulation of a terrain. It works by splitting triangles where more detail is needed and merging them where less detail is needed. The algorithm assigns each triangle in the terrain a priority, usually related to the error decrease if that triangle would be split. The algorithm uses two priority queues, one for triangles that can be split and another for triangles that can be merged. In each step the triangle from the split queue with the highest priority is split, or the triangle from the merge queue with the lowest priority is merged with its neighbours.Prim's algorithm for minimum spanning tree
Using min heap priority queue in Prim's algorithm to find the minimum spanning tree of a connected and undirected graph, one can achieve a good running time. This min heap priority queue uses the min heap data structure which supports operations such as insert, minimum, extract-min, decrease-key. In this implementation, the weight of the edges is used to decide the priority of the vertices. Lower the weight, higher the priority and higher the weight, lower the priority.Parallel priority queue
Parallelization can be used to speed up priority queues. There are three different methods to parallelize priority queues. The easiest approach is to simply parallelize the individual operations, like insert or extractMin. Another method allows the concurrent access of multiple processes to the same priority queue. The last method uses operations that work on elements, instead of just one element. For example, extractMin will remove the first elements with the highest priority.Parallelize individual operations
An easy way to parallelize priority queues is to parallelize the individual operations. That means, the queue works like a sequential one, but the individual operations are sped up by using several processors. The maximum Speedup for this technique is limited by because there are sequential implementations for priority queues whose slowest operation runs in .The operations insert, extract-min, decrease-key and delete can be executed in constant time on a Concurrent Read, Exclusive Write PRAM with processors, where is the size of the priority queue. In the following the queue is implemented as binomial heap. After every operation only three trees of the same order are allowed and the smallest root of the trees of order is smaller than all roots of trees of higher order.
- insert: A new binomial tree with order 0, whose only node contains the element e, is inserted. Then two trees of the same order get linked to a tree of order if there are three trees of order. The tree with the smallest root of the trees with order is not involved in this procedure. Each processor executes this step for the trees of one order. So insert can be run in.
insert
parallelLink
- extract-min: The minimal element is the smallest element of the trees of order 0. This element gets removed. To make sure that the new minimum is in order 0 too, for each order the tree with the smallest root is split into two trees of order. As each processor executes the step for one order, this step can be executed in parallel in. After this step a parallel link is executed. So two trees of order are joint to a tree of order if at least three trees of order exist. This is executable in parallel and so extract-min can be run in constant time.
The concept for constant time insert and extract-min can be extended to achieve a constant time for delete as well. The decrease-key operation can then be implemented as delete and a following insert. So the decrease-key operation is also a constant time operation.
The advantage of this type of parallelizing the priority queue is, that it can be used like a usual sequential one. But the maximum speedup is only. This speedup is further limited in practice, because there is additional effort for the communication between the different processors.
Concurrent parallel access
If the priority queue allows concurrent access, multiple processes can perform operations concurrently on that priority queue. However, this raises two issues. First of all, the definition of the semantics of the individual operations is no longer obvious. For example, if two processes want to extract the element with the highest priority, should they get the same element or different ones? This restricts parallelism on the level of the program using the priority queue. In addition, because multiple processes have access to the same element, this leads to contention.The concurrent access to a priority queue can be implemented on a Concurrent Read, Concurrent Write PRAM model. In the following the priority queue is implemented as a skip list. In addition, an atomic synchronization primitive, CAS, is used to make the skip list lock-free. The nodes of the skip list consists of a unique key, a priority, an array of pointers, for each level, to the next nodes and a delete mark. The delete mark marks if the node is about to be deleted by a process. This ensures that other processes can react to the deletion appropriately.
- insert: First, a new node with a key and a priority is created. In addition, the node is assigned a number of levels, which dictates the size of the array of pointers. Then a search is performed to find the correct position where to insert the new node. The search starts from the first node and from the highest level. Then the skip list is traversed down to the lowest level until the correct position is found. During the search, for every level the last traversed node will be saved as parent node for the new node at that level. In addition, the node to which the pointer, at that level, of the parent node points towards, will be saved as the successor node of the new node at that level. Afterwards, for every level of the new node, the pointers of the parent node will be set to the new node. Finally, the pointers, for every level, of the new node will be set to the corresponding successor nodes.
- extract-min: First, the skip list is traversed until a node is reached whose delete mark is not set. This delete mark is than set to true for that node. Finally the pointers of the parent nodes of the deleted node are updated.
K-element operations
For this type of parallelization new operations are introduced, that operate on elements simultaneously instead of only one.The new operation k_extract-min then deletes the smallest elements of the priority queue and returns those. The queue is parallelized on distributed memory. Each processor has its own local memory and a local priority queue. The elements of the global priority queue are distributed across all processors.
A k_insert operation assigns the elements uniformly random to the processors which insert the elements into their local queues. Note that single elements can still be inserted into the queue. Using this strategy the global smallest elements are in the union of the local smallest elements of every processor with high probability. Thus each processor holds a representative part of the global priority queue.
This property is used when k_extract-min is executed, as the smallest elements of each local queue are removed and collected in a result set. The elements in the result set are still associated with their original processor. The number of elements that is removed from each local queue depends on and the number of processors.
By parallel selection the smallest elements of the result set are determined. With high probability these are the global smallest elements. If not, elements are again removed from each local queue and put into the result set. This is done until the global smallest elements are in the result set. Now these elements can be returned. All other elements of the result set are inserted back into their local queues. The running time of k_extract-min is expected, where and
is the size of the priority queue.
The priority queue can be further improved by not moving the remaining elements of the result set directly back into the local queues after a k_extract-min operation. This saves moving elements back and forth all the time between the result set and the local queues.
By removing several elements at once a considerable speedup can be reached. But not all algorithms can use this kind of priority queue. Dijkstra's algorithm for example can not work on several nodes at once. The algorithm takes the node with the smallest distance from the priority queue and calculates new distances for all its neighbor nodes. If you would take out nodes, working at one node could change the distance of another one of the nodes. So using k-element operations destroys the label setting property of Dijkstra's algorithm.