Hubbry Logo
search
logo

Priority queue

logo
Community Hub0 Subscribers
Read side by side
from Wikipedia

In computer science, a priority queue is an abstract data type similar to a regular queue or stack abstract data type.

In a priority queue, each element has an associated priority, which determines its order of service.[1] Priority queue serves highest priority items first.[1] Priority values have to be instances of an ordered data type, and higher priority can be given either to the lesser or to the greater values with respect to the given order relation. For example, in Java standard library, PriorityQueue's the least elements with respect to the order have the highest priority.[2] This implementation detail is without much practical significance, since passing to the opposite order relation turns the least values into the greatest, and vice versa.

While priority queues are often implemented using heaps, they are conceptually distinct. A priority queue can be implemented with a heap or with other methods; just as a list can be implemented with a linked list or with an array.

Operations

[edit]

A priority queue has the following operations:[3][4][5]

Max-priority queue

[edit]
  • insert(S, element, priority):[4][5] add an element to set S with an associated priority.
  • maximum(S): return the element with highest priority.
    This is also known as "find_max".
  • extract_max(S): remove the element from set S with highest priority, and return it.
    This is also known as "delete",[4] or "extract".[5]
  • increase_key(S, element, k): increase the associated priority with an element to the new value k.

Min-priority queue

[edit]
  • insert(S, element, priority):[4][5] add an element to set S with an associated priority.
  • minimum(S): return the element with lowest priority.
    This is also known as "find_min".
  • extract_min(S): remove the element from set S with lowest priority, and return it.
    This is also known as "delete",[4] or "extract".[5]
  • decrease_key(S, element, k): decrease the associated priority with an element to the new value k.

Stacks and queues can be implemented as particular kinds of priority queues, with the priority determined by the order in which the elements are inserted. In a stack, the priority of each inserted element is monotonically increasing; thus, the last element inserted is always the first retrieved. In a queue, the priority of each inserted element is monotonically decreasing; thus, the first element inserted is always the first retrieved.

In some implementations, if two elements have the same priority, they are served in the same order in which they were enqueued. In other implementations, the order of elements with the same priority is undefined.

Implementation

[edit]

Naive implementations

[edit]

One can create a simple, but inefficient priority queue in a number of ways. These naive implementations can demonstrate the expected behaviour of a priority queue in a simpler manner.

  • insert elements into an unsorted array; find and extract element with highest priority
    Performance: "insert" performs in constant time, where "extract_max" performs in linear time.
insert(element, priority):
    node.element ← element
    node.priority ← priority
    list.append(node)

extract_max():
    highest ← 0
    foreach node in list:
        if highest.priority < node.priority:
            highest ← node
    list.remove(highest)
    return highest.element
  • insert elements into a sorted array; extract first element
    Performance: "insert" performs in linear time, where "extract_max" performs in constant time.
insert(element, priority):
    node.element ← element
    node.priority ← priority
    for i in [0...N]:
        element ← list.get_at_index(i)
        if element.priority < node.priority:
            list.insert_at_index(node, i + 1)
            return

extract_max():
    highest ← list.get_at_index(0)
    list.remove(highest)
    return highest.element

Usual implementation

[edit]

To improve performance, priority queues are typically based on a heap, giving performance for inserts and removals, and to build the heap initially from a set of elements. Variants of the basic heap data structure such as pairing heaps or Fibonacci heaps can provide better bounds for some operations.[6]

Alternatively, when a self-balancing binary search tree is used, insertion and removal also take time, although building trees from existing sequences of elements takes time; this is typical where one might already have access to these data structures, such as with third-party or standard libraries. From a space-complexity standpoint, using self-balancing binary search tree with linked list takes more storage, since it requires to store extra references to other nodes.

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

[edit]

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 list, 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.[7]: 374 
  • A van Emde Boas tree supports the minimum, maximum, insert, delete, search, extract-min, extract-max, predecessor and successor] operations in time, but has a space cost for small queues of about , where is the number of bits in the priority value.[8] The space can be reduced significantly with hashing.
  • The Fusion tree by Fredman and Willard implements the minimum operation in 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."[9]

For applications that do many "peek" operations for every "extract-min" operation, the time complexity for peek actions can be reduced to in all tree and heap implementations by caching the highest priority element after every insertion and removal. For insertion, this adds at most a constant cost, since the newly inserted element is compared only to the previously cached minimum element. For deletion, this at most adds an additional "peek" cost, which is typically cheaper than the deletion cost, so overall time complexity is not significantly impacted.

Monotone priority queues are specialized queues that are optimized for the case where no item is ever inserted that has a lower priority (in the case of min-heap) than any item previously extracted. This restriction is met by several practical applications of priority queues.

Summary of running times

[edit]

Here are time complexities[10] of various heap data structures. The abbreviation am. indicates that the given complexity is amortized, otherwise it is a worst-case complexity. For the meaning of "O(f)" and "Θ(f)" see Big O notation. Names of operations assume a min-heap.

Operation find-min delete-min decrease-key insert meld make-heap[a]
Binary[10] Θ(1) Θ(log n) Θ(log n) Θ(log n) Θ(n) Θ(n)
Skew[11] Θ(1) O(log n) am. O(log n) am. O(log n) am. O(log n) am. Θ(n) am.
Leftist[12] Θ(1) Θ(log n) Θ(log n) Θ(log n) Θ(log n) Θ(n)
Binomial[10][14] Θ(1) Θ(log n) Θ(log n) Θ(1) am. Θ(log n)[b] Θ(n)
Skew binomial[15] Θ(1) Θ(log n) Θ(log n) Θ(1) Θ(log n)[b] Θ(n)
2–3 heap[17] Θ(1) O(log n) am. Θ(1) Θ(1) am. O(log n)[b] Θ(n)
Bottom-up skew[11] Θ(1) O(log n) am. O(log n) am. Θ(1) am. Θ(1) am. Θ(n) am.
Pairing[18] Θ(1) O(log n) am. o(log n) am.[c] Θ(1) Θ(1) Θ(n)
Rank-pairing[21] Θ(1) O(log n) am. Θ(1) am. Θ(1) Θ(1) Θ(n)
Fibonacci[10][22] Θ(1) O(log n) am. Θ(1) am. Θ(1) Θ(1) Θ(n)
Strict Fibonacci[23][d] Θ(1) Θ(log n) Θ(1) Θ(1) Θ(1) Θ(n)
Brodal[24][d] Θ(1) Θ(log n) Θ(1) Θ(1) Θ(1) Θ(n)[25]
  1. ^ make-heap is the operation of building a heap from a sequence of n unsorted elements. It can be done in Θ(n) time whenever meld runs in O(log n) time (where both complexities can be amortized).[11][12] Another algorithm achieves Θ(n) for binary heaps.[13]
  2. ^ a b c For persistent heaps (not supporting decrease-key), a generic transformation reduces the cost of meld to that of insert, while the new cost of delete-min is the sum of the old costs of delete-min and meld.[16] Here, it makes meld run in Θ(1) time (amortized, if the cost of insert is) while delete-min still runs in O(log n). Applied to skew binomial heaps, it yields Brodal-Okasaki queues, persistent heaps with optimal worst-case complexities.[15]
  3. ^ Lower bound of [19] upper bound of [20]
  4. ^ a b Brodal queues and strict Fibonacci heaps achieve optimal worst-case complexities for heaps. They were first described as imperative data structures. The Brodal-Okasaki queue is a persistent data structure achieving the same optimum, except that decrease-key is not supported.

Equivalence of priority queues and sorting algorithms

[edit]

Using a priority queue to sort

[edit]

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 n
Selection sort Unordered Array
Insertion sort Ordered Array
Tree sort Self-balancing binary search tree

Using a sorting algorithm to make a priority queue

[edit]

A sorting algorithm can also be used to implement a priority queue. Specifically, Thorup says:[26]

We present a general deterministic linear space reduction from priority queues to sorting implying that if we can sort up to keys in time per key, then there is a priority queue supporting delete and insert in time and find-min in constant time.

That is, if there is a sorting algorithm which can sort in time per key, where is some function of and word size,[27] then one can use the given procedure to create a priority queue where pulling the highest-priority element is time, and inserting new elements (and deleting elements) is time. For example, if one has an sort algorithm, one can create a priority queue with pulling and insertion.

Libraries

[edit]

A priority queue is often considered to be a "container data structure".

The Standard Template Library (STL), and the C++ 1998 standard, specifies std::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 (defaults to less<T> if unspecified), the underlying container for storing the data structures (defaults to std::vector<T>), and two iterators to the beginning and end of a sequence. Unlike actual STL containers, it does not allow iteration of its elements (it strictly adheres to its abstract data type definition). 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 heapq module implements a binary min-heap on top of a list.

Java's library contains a PriorityQueue (java.util.PriorityQueue) class, which implements a min-priority-queue as a binary heap.

.NET's library contains a System.Collections.Generic.PriorityQueue class, which implements an array-backed, quaternary min-heap.

Scala's library contains a scala.collection.mutable.PriorityQueue class, which implements a max-priority-queue.

Go's library contains a container/heap module, which implements a min-heap on top of any compatible data structure.

Rust's standard library contains a std::collections::BinaryHeap struct, which implements a priority queue with a binary heap.

The Standard PHP Library extension contains the class SplPriorityQueue.

Apple's Core Foundation framework contains a CFBinaryHeap structure, which implements a min-heap.

Applications

[edit]

Bandwidth management

[edit]

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 (such as real-time traffic, e.g. an RTP stream of a VoIP connection) 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 (MAC) sub-layer to ensure that high-priority applications (such as VoIP or IPTV) experience lower latency than other applications which can be served with best-effort service. Examples include IEEE 802.11e (an amendment to IEEE 802.11 which provides quality of service) and ITU-T G.hn (a standard for high-speed local area network using existing home wiring (power lines, phone lines and coaxial cables).

Usually a limitation (policer) 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

[edit]

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 (computing), queueing theory

Dijkstra's algorithm

[edit]

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.

If instead, a graph is stored as node objects, and priority-node pairs are inserted into a heap, altering the priority of a particular vertex is not necessary if one tracks visited nodes. Once a node is visited, if it comes up in the heap again (having had a lower priority number associated with it earlier), it is popped-off and ignored.

Huffman coding

[edit]

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

[edit]

Best-first search 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 (also known as the fringe) is used to keep track of unexplored routes; the one for which the estimate (a lower bound in the case of A*) 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

[edit]

The Real-time Optimally Adapting Meshes (ROAM) 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

[edit]

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.[28] 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.[29]

Parallel priority queue

[edit]

Parallelization can be used to speed up priority queues, but requires some changes to the priority queue interface. The reason for such changes is that a sequential update usually only has or cost, and there is no practical gain to parallelize such an operation. One possible change is to allow the concurrent access of multiple processors to the same priority queue. The second possible change is to allow batch operations that work on elements, instead of just one element. For example, extractMin will remove the first elements with the highest priority.

Concurrent parallel access

[edit]

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.

Node 3 is inserted and sets the pointer of node 2 to node 3. Immediately after that, node 2 is deleted and the pointer of node 1 is set to node 4. Now node 3 is no longer reachable.

The concurrent access to a priority queue can be implemented on a Concurrent Read, Concurrent Write (CRCW) PRAM model. In the following the priority queue is implemented as a skip list.[30][31] 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(e): 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.

If the concurrent access to a priority queue is allowed, conflicts may arise between two processes. For example, a conflict arises if one process is trying to insert a new node, but at the same time another process is about to delete the predecessor of that node.[30] There is a risk that the new node is added to the skip list, yet it is not longer reachable. (See image)

K-element operations

[edit]

In this setting, operations on a priority queue is generalized to a batch of elements. For instance, k_extract-min deletes the smallest elements of the priority queue and returns those.

In a shared-memory setting, the parallel priority queue can be easily implemented using parallel binary search trees and join-based tree algorithms. In particular, k_extract-min corresponds to a split on the binary search tree that has cost and yields a tree that contains the smallest elements. k_insert can be applied by a union of the original priority queue and the batch of insertions. If the batch is already sorted by the key, k_insert has cost. Otherwise, we need to first sort the batch, so the cost will be . Other operations for priority queue can be applied similarly. For instance, k_decrease-key can be done by first applying difference and then union, which first deletes the elements and then inserts them back with the updated keys. All these operations are highly parallel, and the theoretical and practical efficiency can be found in related research papers.[32][33]

The rest of this section discusses a queue-based algorithm on distributed memory. We assume each processor has its own local memory and a local (sequential) priority queue. The elements of the global (parallel) priority queue are distributed across all processors.

k_extract-min is executed on a priority queue with three processors. The green elements are returned and removed from the priority queue.

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 . [34] 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.[34]

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.

See also

[edit]

References

[edit]

Further reading

[edit]
[edit]
Revisions and contributorsEdit on WikipediaRead on Wikipedia
from Grokipedia
A priority queue is an abstract data type that stores a collection of elements, each associated with a key representing its priority under a total order, allowing elements to be inserted and removed based on priority rather than insertion order.[1][2] Unlike standard queues, which process elements in FIFO order, priority queues deprioritize lower-key elements to ensure the highest (or lowest, depending on convention) priority item is always accessible first.[3] This structure is fundamental in algorithm design for tasks requiring selective processing, such as event simulation or resource allocation.[2] The core operations of a priority queue include insertion of a new element with its key, and removal of the element with the maximum (or minimum) key, both typically achieving logarithmic time complexity in efficient implementations.[2][3] Additional operations often supported are peeking at the maximum/minimum without removal, checking emptiness, and querying the size, all while maintaining the total order property where keys are comparable, reflexive, antisymmetric, and transitive.[1] These operations enable dynamic management of priorities, with some variants allowing priority modifications, though this requires knowing the element's position for efficiency.[3] Priority queues can be implemented using various underlying structures, balancing trade-offs in time complexity for insertions and removals.[1] Simple array-based approaches, such as unsorted lists (O(1) insert, O(n) remove-max) or sorted lists (O(n) insert, O(1) remove-max), provide basic functionality but scale poorly.[2] More efficient realizations employ binary heaps, complete binary trees satisfying the heap-order property (parent key greater than or equal to children in max-heaps), which support both insert and remove-max in O(log n) time via up-heap and down-heap bubbling.[3][1] Balanced search trees offer similar logarithmic performance with added flexibility for other queries.[1] In practice, priority queues underpin algorithms like heapsort, which achieves O(n log n) sorting by repeatedly removing the maximum from a heap, and are widely used in applications such as Dijkstra's shortest-path algorithm, job scheduling, and Huffman coding for data compression.[3][1] Their ability to handle dynamic priorities efficiently makes them essential in operating systems, networking, and real-time systems.[2]

Fundamentals

Definition and motivation

A priority queue is an abstract data type similar to a regular queue or stack, but it stores a collection of elements where each is associated with a priority value, and elements are dequeued based on priority order rather than first-in, first-out (FIFO) insertion sequence.[2][1] This structure ensures that the element with the highest (or lowest, depending on convention) priority is always accessible for removal, providing selective access to the most relevant item in the collection.[2] The motivation for priority queues arises in scenarios requiring urgent or importance-based processing over strict temporal order, such as task scheduling in operating systems, where higher-priority processes are executed before lower ones to optimize resource allocation.[4] They are also essential in event-driven simulations, where events like arrivals or state changes are processed in priority order, often timestamped, to model real-world systems accurately.[5] A practical example is medical triage in emergency rooms, where patients are prioritized by condition severity—such as treating critical cases immediately—over arrival time to maximize life-saving outcomes.[6] Priority queues support dynamic insertion and removal of elements while maintaining priority ordering, with priorities typically represented as numeric keys or via custom comparators for more complex types; ties in priority are often resolved using secondary criteria, like insertion timestamps, to ensure deterministic behavior.[2][7] The concept was formalized in 1964 by J. W. J. Williams, who introduced it in the context of heap structures for selection and sorting tasks.

Comparison to queues and stacks

A standard queue is an abstract data type (ADT) that adheres to the First-In-First-Out (FIFO) principle, where elements are inserted at the rear (enqueue) and removed from the front (dequeue), preserving the order of insertion.[8] In contrast, a priority queue dequeues the element with the highest (or lowest, depending on convention) priority regardless of its insertion position, allowing non-sequential access based on associated priority keys.[2] This enables priority queues to handle scenarios where urgency or importance dictates processing order, such as in task scheduling systems.[9] A stack, another fundamental ADT, follows the Last-In-First-Out (LIFO) principle, with insertions (push) and removals (pop) occurring exclusively at the top, effectively reversing the order of operations relative to insertion.[8] Priority queues differ fundamentally by ignoring insertion order entirely and instead extracting based on priority, which may require maintaining a partial order across all elements rather than endpoint access.[10] Stacks are commonly used for recursion or undo mechanisms in applications like expression evaluation, where the most recent item must be processed next.[8] Key differences between these structures lie in their access patterns and use cases: queues and stacks permit only endpoint operations for efficient O(1) time complexity, enforcing strict FIFO or LIFO sequencing suitable for order-preserving tasks like breadth-first search or function call management.[2] Priority queues, however, support priority-based extraction, often at O(log n) cost, ideal for urgency-driven processing in simulations or operating system schedulers where elements must be dynamically reordered.[9] From an ADT perspective, all three are collections of elements, but priority queues uniquely require a total order on priorities (via comparable keys) to determine extraction, distinguishing them from the positional ordering in queues and stacks.[8] To illustrate these contrasts, consider simple pseudocode for core operations: Queue operations:
enqueue(Q, x):  // Add to rear
    [append](/page/Append) x to Q

dequeue(Q):     // Remove from front
    return remove front of Q
[2] Stack operations:
push(S, x):     // Add to top
    [append](/page/Append) x to S

pop(S):         // Remove from top
    return remove end of S
[8] Priority queue operations:
insert(PQ, x):  // Add with priority key
    add x to PQ and maintain priority order

extract-max(PQ): // Remove highest priority
    return and remove max-key element from PQ
[10]

Core Operations

Insertion and extraction

The insertion operation in a priority queue adds a new element paired with its priority value to the data structure, preserving the overall priority ordering without immediately altering the positions of existing elements. In a max-priority queue, higher numerical priority values denote greater urgency or importance, ensuring that elements with larger priorities are positioned to be serviced ahead of those with smaller ones.[2][11] The extract-max operation removes and returns the element possessing the highest priority from the queue, effectively dequeuing the most urgent item while maintaining the integrity of the remaining structure. For an empty queue, this operation commonly raises an exception to indicate the error or returns a sentinel value like null to signal the absence of elements.[2][12] A high-level pseudocode representation of extract-max, at the abstract data type level, is as follows:
function extractMax(PQ):
    if PQ is empty:
        return null  // or [raise](/page/Raise!) an exception
    else:
        maxElement = the element with highest priority
        remove maxElement from PQ
        return maxElement
[13][14] Max-priority queues, which assume priorities escalate toward the maximum value, find frequent application in graph algorithms such as Dijkstra's shortest path method, where selecting the node with the highest tentative priority (often adjusted via distances) drives the relaxation process.[15] For instance, in task scheduling systems, insertion might involve adding jobs with priority scores reflecting urgency levels (e.g., 10 for critical, 5 for routine), while extraction retrieves the job with the maximum score, such as processing a high-priority deadline task before others.[2] A min-priority queue serves as a symmetric variant, inverting the priority ordering to favor minimum values.[11]

Priority updates and peeking

In priority queues, the peek operation provides a non-destructive way to access the element with the highest priority without removing it from the structure. This is particularly useful for inspecting the current top priority before deciding on further actions, such as in event simulation where the next event needs to be examined.[16] In typical implementations using heaps, the peek function simply returns the root element if the queue is not empty, assuming the root holds the extremal priority. The following pseudocode illustrates a basic peek implementation in an abstract priority queue:
function peek():
    if queue is empty:
        return null  // or raise an [error](/page/Error)
    return root  // the highest-priority element
Priority update operations, such as decrease-key and increase-key, allow modification of an element's priority after insertion, ensuring the queue's ordering is restored through adjustments like bubbling up or down in the structure. These updates are essential for scenarios where priorities evolve dynamically, avoiding the inefficiency of deletion and re-insertion. In a min-heap-based queue, decrease-key lowers the key value (thereby raising the priority) and percolates the element upward, while increase-key raises the key and percolates downward.[17] The symmetric increase-key operation serves a similar role in max-priority contexts, though decrease-key is more commonly emphasized in min-priority settings.[18] A min-priority queue inverts the standard max-priority model by assigning higher priority to elements with smaller key values, where the smallest key represents the top priority. This variant supports peek via a minimum operation that returns the smallest-key element without removal, and extract-min that removes and returns it. Min-priority queues are widely used in shortest-path algorithms, such as Dijkstra's, where node distances start high and are progressively decreased to reflect better paths found.[19] Certain advanced priority queue implementations also support a union or merge operation to combine two disjoint queues into a single one that preserves the overall priority ordering. This facilitates efficient aggregation of priority sets in divide-and-conquer algorithms. As an example, consider a task management system where tasks are queued by urgency level (lower number indicating higher urgency in a min-priority queue). If midway through processing, new information elevates a task's urgency—say, reducing its key from 5 to 2—a decrease-key operation updates its position, bubbling it toward the front without disrupting other elements or requiring full re-queueing.[17] This maintains responsiveness in real-time scheduling while leveraging the queue's structure for quick adjustments.

Implementations

Unsorted and sorted lists

One of the simplest ways to implement a priority queue is using an unsorted list, where elements are stored in arbitrary order without maintaining any specific sequence based on priorities.[20] In this approach, insertion is efficient as new elements can be appended to the end of the list in constant time, O(1), regardless of the current size.[21] However, extraction of the maximum (or minimum) priority element requires scanning the entire list to identify the highest-priority item, followed by its removal, resulting in O(n time complexity in the worst case, where n is the number of elements.[20] This makes the unsorted list suitable for scenarios with a high ratio of insertions to extractions, such as when building the queue with many additions before any removals.[22] An alternative basic implementation uses a sorted list, where elements are maintained in non-decreasing (or non-increasing) order of priority.[20] Insertion involves first locating the appropriate position using binary search, which takes O(log n) time, but then shifting existing elements to accommodate the new one, which requires O(n) time due to the linear relocation in a list structure.[23] Extraction of the maximum priority element is then straightforward, as it is always at one end of the list, allowing removal in O(1) time.[20] This variant performs better in situations dominated by extractions after initial insertions, though the overall worst-case time for key operations remains O(n).[21] Both unsorted and sorted list implementations offer trade-offs in efficiency: the unsorted list prioritizes fast insertions at the expense of slower extractions, while the sorted list favors quick extractions but slows insertions.[20] Despite their simplicity, these approaches are generally inefficient for large-scale applications due to the linear factors in their primary operations, though they provide a foundational understanding before exploring more balanced structures like heaps.[22] To illustrate the extract-max operation in an unsorted list, the following pseudocode demonstrates iterating through the list to find and remove the highest-priority element:
function extractMax(priorityQueue):
    if priorityQueue is empty:
        return null
    maxIndex = 0
    maxPriority = priorityQueue[0].priority
    for i from 1 to length(priorityQueue) - 1:
        if priorityQueue[i].priority > maxPriority:
            maxPriority = priorityQueue[i].priority
            maxIndex = i
    maxElement = priorityQueue[maxIndex]
    remove priorityQueue at maxIndex  // O(n) shift in array-based list
    return maxElement
[1] These list-based methods are primarily used in educational contexts to introduce priority queue concepts or in applications with small n, where the overhead of more complex data structures is unnecessary.[24] For larger datasets, they have been largely superseded by heap-based implementations that achieve logarithmic performance for both insertions and extractions.[2]

Balanced binary search trees

Priority queues can also be implemented using balanced binary search trees (BSTs), such as AVL trees or red-black trees, where elements are stored ordered by their keys.[2] Insertion and extraction of the minimum (or maximum) both take O(log n) time due to the tree's height, with extract-min simply removing the leftmost (or rightmost) node. These structures support additional operations like searching for a specific key in O(log n) time and deleting arbitrary elements efficiently, making them suitable when such flexibility is needed beyond basic priority queue operations. However, they typically require more space and have higher constant factors compared to heap implementations.[1]

Binary heaps

A binary heap is a common implementation of a priority queue based on a complete binary tree that satisfies the heap property: in a max-heap, the key of each node is greater than or equal to the keys of its children, ensuring the maximum key is at the root.[25] This structure provides efficient support for insertion and extraction operations, each in O(logn)O(\log n) time, where nn is the number of elements, outperforming the linear-time operations of simpler list-based implementations.[26] The binary heap is typically represented as an array to exploit the complete tree property, where the tree is filled level by level from left to right, with no gaps except possibly at the last level. For a 0-based array index ii, the left child is at 2i+12i + 1 and the right child at 2i+22i + 2, while the parent of a node at ii (for i>0i > 0) is at (i1)/2\lfloor (i - 1)/2 \rfloor.[25] This implicit indexing avoids explicit pointers, enabling compact storage and fast navigation. The original formulation of the binary heap as a data structure for heapsort, introduced by J. W. J. Williams in 1964, used this array-based complete binary tree to facilitate efficient sorting.[27] To construct a binary heap from an unsorted array of nn elements, the build-heap procedure starts from the bottom of the tree and applies heapify-down (also called sink) operations upward from the last non-leaf node at index n/21\lfloor n/2 \rfloor - 1 to the root. This bottom-up approach, developed by Robert W. Floyd in 1964 as an improvement to Williams' initial method, achieves O(n)O(n) time complexity overall, rather than the O(nlogn)O(n \log n) of naive repeated insertions.[28] The heapify-down operation at a node compares it with its children, swaps with the larger child if necessary to restore the heap property, and recurses downward, taking O(logn)O(\log n) time per call but amortizing to linear time across the build.[25] Insertion into a max-heap adds the new element at the end of the array (the next available leaf position) and then performs heapify-up (also called swim) to bubble it upward by repeatedly swapping with its parent if it is larger, until the heap property is satisfied; this takes O(logn)O(\log n) time in the worst case.[26] Extraction of the maximum element swaps the root with the last element, reduces the heap size by one, and applies heapify-down from the root to restore the property, again in O(logn)O(\log n) time.[25] Peeking at the maximum is O(1)O(1), simply accessing the root. The following pseudocode illustrates these operations for a max-heap (adapted from standard implementations): Insert (push):
function insert(heap, key):
    heap.append(key)  // Add to end
    i = len(heap) - 1
    heapify_up(heap, i)

function heapify_up(heap, i):
    while i > 0:
        parent = (i - 1) // 2
        if heap[i] <= heap[parent]:
            break
        swap(heap[i], heap[parent])
        i = parent
Extract-max (pop):
function extract_max(heap):
    if len(heap) == 0:
        return null
    max = heap[0]
    heap[0] = heap.pop()  // Move last to root
    if len(heap) > 0:
        heapify_down(heap, 0)
    return max

function heapify_down(heap, i):
    n = len(heap)
    while True:
        left = 2 * i + 1
        right = 2 * i + 2
        largest = i
        if left < n and heap[left] > heap[largest]:
            largest = left
        if right < n and heap[right] > heap[largest]:
            largest = right
        if largest == i:
            break
        swap(heap[i], heap[largest])
        i = largest
[26] A min-heap variant inverts the comparisons in the heap property and operations, so the parent key is less than or equal to its children's keys, placing the minimum at the root; this is useful for min-priority queues and is achieved by swapping greater-than and less-than conditions in the algorithms.[26] The space complexity of a binary heap is O(n)O(n), as it uses a single contiguous array of size nn to store all elements without additional overhead beyond the keys themselves.[25]

Advanced heap variants

Fibonacci heaps represent a significant advancement over binary heaps by achieving better amortized time bounds for key operations through a more flexible structure consisting of a collection of heap-ordered trees connected via a root list.[29] Introduced by Michael L. Fredman and Robert E. Tarjan in 1984 and published in 1987, these heaps support insertion and decrease-key operations in amortized O(1) time, extract-min in amortized O(log n) time, and deletion in amortized O(log n) time.[29] The efficiency stems from lazy merging of trees during unions (linking by size or degree) and delayed propagation of structural changes via marking for lazy deletions, which defers cuts until necessary during extract-min to consolidate the forest.[29] This amortized analysis relies on a potential function that accounts for the number of trees and marked nodes, enabling applications like faster shortest-path algorithms.[29] Pairing heaps offer a simpler alternative to Fibonacci heaps while maintaining competitive performance, particularly in practice, by avoiding complex degree tracking and potential functions in their basic implementation.[30] Developed by Michael L. Fredman, Robert Sedgewick, Daniel D. Sleator, and Robert E. Tarjan in 1986, pairing heaps are represented as heap-ordered trees where unions are performed by recursively pairing roots in a tournament-like manner.[30] They support insert, meld, and decrease-key in amortized O(1) time, with extract-min in amortized O(log n) time, using a two-pass linking strategy for decrease-key that first attaches the node to its parent and then restructures via sibling pairing.[30] Although their worst-case analysis was refined later, pairing heaps excel in empirical speed due to their straightforward code and reduced constant factors.[30] d-ary heaps generalize binary heaps to trees where each node has up to dd children, allowing tunable trade-offs between operation speeds based on the arity dd. As detailed in standard algorithmic texts, the height of a d-ary heap is O(logdn)O(\log_d n), making insert operations, which bubble up the tree, take O(logdn)=O(lognlogd)O(\log_d n) = O(\frac{\log n}{\log d}) time, while extract-min, which sifts down and may compare up to dd children, takes O(dlogdn)O(d \log_d n) time. For d>2d > 2, larger arities reduce the tree height for faster extracts at the cost of more comparisons per level, proving useful in scenarios like Dijkstra's algorithm where decrease-key is less frequent than extracts. The structure maintains the complete d-ary tree property in array representation, with parent-child indexing adjusted for dd children. Soft heaps introduce approximation to priority queues, permitting a controlled number of "corruptions" (errors in reported minima) to achieve faster operations, particularly for selection problems like finding medians.[31] Invented by Bernard Chazelle in 2000, a soft heap supports insert in amortized O(1) time, meld in O(1), find-min in O(1), and delete in amortized O(log n / log log n) time, while allowing up to εn\varepsilon n corruptions for error rate ε\varepsilon.[31] The design uses a forest of binomial-like trees with "carpooling" to lazily release excess elements, ensuring the minimum is approximate but enabling optimal error rates for applications in deterministic linear-time selection and minimum spanning trees.[31] Recent post-2020 research has explored cache-oblivious variants of heap structures, leveraging van Emde Boas layouts to minimize cache misses and enhance parallelism in multi-core environments without tuning to specific cache parameters.[32] For instance, generalizations of tree layouts to B-tree-like heaps achieve O(log_B n) I/O complexity for operations, where B is the cache line size, improving scalability on modern hardware.[32]

Runtime analysis

The runtime performance of priority queues varies significantly depending on the underlying implementation, with key operations including insertion, extraction of the minimum (or maximum), decrease-key (updating a priority to a lower value), and building the structure from nn elements. These complexities are analyzed under the assumption that priority comparisons take constant O(1)O(1) time.[33] The following table summarizes the time complexities for common implementations, distinguishing between worst-case and amortized bounds where applicable:
OperationUnsorted ListSorted ListBinary HeapFibonacci Heap
InsertO(1)O(1)O(n)O(n)O(logn)O(\log n)O(1)O(1) amortized
Extract-minO(n)O(n)O(1)O(1)O(logn)O(\log n)O(logn)O(\log n) amortized
Decrease-keyO(n)O(n)O(n)O(n)O(logn)O(\log n)O(1)O(1) amortized
Build (nn elements)O(n)O(n)O(nlogn)O(n \log n)O(n)O(n)O(n)O(n)
These bounds are established for unsorted and sorted lists using simple array or linked list structures, binary heaps via array-based complete binary trees, and Fibonacci heaps through a collection of heap-ordered trees supporting lazy merging.[33][34][35] In Fibonacci heaps, the O(1)O(1) amortized time for decrease-key and insert arises from potential function analysis, which accounts for the cost of structural changes like linking and cutting trees over a sequence of operations, rather than charging them immediately; worst-case times for these can reach O(logn)O(\log n).[35] All standard priority queue implementations require O(n)O(n) space to store nn elements, as each element must be represented along with its priority. However, pointer-based variants like Fibonacci heaps incur additional auxiliary space due to multiple child pointers per node (up to degree Θ(logn)\Theta(\log n) in the worst case), leading to higher memory overhead compared to the compact O(n)O(n) array storage in binary heaps.[34][35] Practical runtime is also influenced by factors beyond asymptotic complexity, such as cache performance: array-based structures like binary heaps benefit from sequential memory access and better locality, reducing cache misses, whereas pointer-based implementations like Fibonacci heaps suffer from irregular access patterns and higher miss rates.[36] Additionally, experimental studies from the late 1980s, including benchmarks on decrease-key heavy workloads, showed that pairing heaps—a simpler self-adjusting variant—often outperform Fibonacci heaps in practice due to lower constant factors and easier implementation, despite lacking formal O(1)O(1) amortized decrease-key bounds.[37]

Theoretical Foundations

Equivalence to sorting algorithms

A priority queue maintains a partial ordering on its elements through properties such as the heap invariant, where for every node, its priority is greater than or equal to (in a max-heap) or less than or equal to (in a min-heap) the priorities of its children. This establishes a partial sort, as the structure ensures that the highest (or lowest) priority element is always accessible at the root, while the remaining elements satisfy a recursive ordering without guaranteeing a total linear order like a fully sorted array.[38] The connection to sorting arises from the selection problem, where repeatedly extracting the minimum (or maximum) element from a priority queue containing n elements produces them in sorted order. Inserting all n elements followed by n extractions simulates a sorting process, directly linking the efficiency of priority queue operations to sorting performance. In the comparison model of computation, where decisions rely solely on pairwise comparisons, a priority queue supporting insert and extract-min in O(log n) time per operation enables sorting in O(n log n) time, matching the known lower bound for comparison-based sorting algorithms, which requires at least Ω(n log n) comparisons in the worst case due to the decision tree height needed to distinguish n! permutations.[38][39] Conversely, efficient sorting implies efficient priority queues through reductions that simulate priority queue operations using a sorter. A general deterministic linear-space reduction transforms a sorting oracle into a priority queue by sorting batches of elements and maintaining sorted access to simulate inserts and extracts, ensuring that the per-operation cost of the priority queue matches the per-key sorting cost asymptotically. This equivalence holds in the comparison model, establishing that priority queues and sorting are computationally equivalent up to constant factors.[40][39] The theoretical ties between priority queues and sorting efficiency trace back to the 1960s, when J. W. J. Williams introduced the binary heap as a data structure for achieving O(n log n) sorting through partial ordering maintenance. Binary heaps serve as a practical bridge to this equivalence by realizing the O(log n) operations that align with sorting lower bounds.

Heap sort and selection sort connections

One approach to sorting using a priority queue involves inserting all nn elements into the queue, which requires O(nlogn)O(n \log n) time for a binary heap implementation, followed by nn extractions of the maximum (or minimum) element, each taking O(logn)O(\log n) time, for a total of O(nlogn)O(n \log n) time.[2] This method, often called priority queue sort or PQ-sort, produces a stable sort if the priority queue implementation preserves the order of elements with equal priorities, such as by using insertion order as a secondary key.[2] The total time complexity can be expressed more precisely as i=1nloginlogn1.44n\sum_{i=1}^n \log i \approx n \log n - 1.44n, reflecting the varying heap sizes during extractions. Heap sort represents an efficient, in-place adaptation of this priority queue-based sorting paradigm, leveraging the binary heap data structure. The algorithm begins with a build-heap operation on the array, which constructs a max-heap in O(n)O(n) time using a bottom-up heapify process starting from the last non-leaf node. It then performs nn iterations of swapping the root (maximum element) with the last unsorted element, reducing the heap size by one, and heapifying the reduced heap in O(logn)O(\log n) time per iteration, yielding an overall O(nlogn)O(n \log n) time complexity.[27] Originally proposed by Williams in 1964, heap sort is unstable due to the swapping mechanism, which can reorder equal elements, but it achieves optimal asymptotic performance and requires no additional space beyond the input array.[27] Selection sort can also be viewed through the lens of priority queue operations, where the unsorted portion of the array acts as an implicit priority queue implemented via an unsorted list. In each of nn passes, the maximum element is selected and swapped to the end of the prefix, mimicking an extract-max operation; however, finding the maximum in an unsorted list takes O(n)O(n) time per pass, resulting in O(n2)O(n^2) overall time. For linear-time selection of the kk-th largest element (a building block for sorting), the median-of-medians algorithm partitions the array around a good pivot in O(n)O(n) time, enabling recursive selection.[41] Conversely, a priority queue can be constructed from a sorted array using a binary search tree-based implementation, where the sorted elements seed the tree and balance is maintained to support logarithmic-time insertions, extractions, priority updates, and peeks.[2]

Applications

Shortest path and spanning tree algorithms

Priority queues play a central role in efficient implementations of shortest path and minimum spanning tree algorithms on graphs, particularly by managing the selection of the next vertex or edge based on priority values such as distances or weights.[42] Dijkstra's algorithm, introduced in 1959, computes the shortest paths from a single source vertex to all other vertices in a graph with non-negative edge weights.[43] It employs a min-priority queue to store tentative distances to unvisited vertices, where the queue holds pairs of (distance, vertex). The algorithm repeatedly extracts the vertex with the minimum distance using extract-min, marks it as visited, and relaxes the distances to its adjacent vertices via decrease-key operations if shorter paths are found. With a binary heap implementation, the time complexity is O((V + E) log V), where V is the number of vertices and E is the number of edges, due to V extract-min operations and up to E decrease-key operations, each taking O(log V) time.[42] The following pseudocode illustrates the core use of the priority queue in Dijkstra's algorithm:
Initialize priority queue Q with (0, source) and infinity for others
While Q is not empty:
    u = extract-min(Q)
    If u is marked visited, continue
    Mark u visited
    For each neighbor v of u:
        If dist[v] > dist[u] + weight(u, v):
            dist[v] = dist[u] + weight(u, v)
            decrease-key(Q, v, dist[v])
This structure ensures that the frontier of unprocessed vertices is always expanded by the closest one, guaranteeing correctness for non-negative weights.[43] Prim's algorithm, proposed in 1957, constructs a minimum spanning tree (MST) for a connected, undirected graph by growing a tree from an arbitrary starting vertex, adding the minimum-weight edge connecting a tree vertex to a non-tree vertex at each step.[44] It uses a priority queue to track the minimum key (edge weight) for each non-tree vertex, similar to Dijkstra's setup, where the queue prioritizes edges by weight. Extract-min selects the next vertex to add to the tree, and decrease-key updates keys for adjacent non-tree vertices. Using a binary heap, the time complexity is O(E log V), as the algorithm performs V extract-min and up to E decrease-key operations.[42] The priority queue enables these algorithms to efficiently manage the dynamic set of candidate vertices or edges, avoiding the need to scan all elements repeatedly and achieving logarithmic-time access to the minimum priority element. For sparse graphs, using a Fibonacci heap instead of a binary heap in Dijkstra's algorithm reduces the time complexity to O(E + V log V), leveraging amortized O(1) decrease-key operations.

Huffman coding and data compression

Huffman coding, a lossless data compression technique, employs a min-priority queue to construct an optimal prefix code tree based on symbol frequencies, assigning shorter codes to more frequent symbols to minimize the average code length. The algorithm begins by creating leaf nodes for each unique symbol, with their frequencies as priorities, and inserting them into the priority queue. It then repeatedly extracts the two nodes with the minimum frequencies, merges them into a new internal node whose frequency is the sum of its children, and reinserts this new node back into the queue. This process continues until only one node remains, forming the root of a binary tree that defines the variable-length codes via traversal paths from root to leaves.[45][46] The resulting Huffman tree enables encoding where the path from root to a leaf (left for 0, right for 1) yields the prefix-free code for that symbol, ensuring no code is a prefix of another and achieving optimality for known frequencies under the Kraft inequality. Using a binary heap as the priority queue implementation, the construction requires O(n) extract-min and insert operations, each taking O(log n) time, yielding an overall time complexity of O(n log n) for n symbols.[47][48] For illustration, consider symbols A (frequency 5), B (3), and C (2). The priority queue initially holds these leaves. First, extract C and B (mins 2 and 3), merge into node X (frequency 5), reinsert X. Now queue has A (5) and X (5); extract both, merge into root Y (10). The tree assigns code 0 to A, 10 to B, and 11 to C, reducing total bits for a message like ABC from 24 (fixed 8-bit) to 5.[46] Huffman coding underpins entropy encoding in standards like ZIP (via DEFLATE, combining LZ77 with dynamic Huffman trees) and JPEG (for baseline lossless compression of quantized DCT coefficients). It also approximates aspects of arithmetic coding by providing efficient variable-length codes for discrete symbols in broader compression pipelines.[49] An extension, adaptive Huffman coding, builds and updates the tree incrementally for streaming data without prior frequency knowledge, using techniques like the FGK algorithm to rebalance nodes as symbols arrive. This variant, developed in the 1970s, addresses dynamic sources by periodically adapting code lengths based on running frequency estimates.[50]

Discrete event simulation and search algorithms

In discrete event simulation (DES), a min-priority queue is essential for managing the event list, where each event is associated with a timestamp serving as its priority key. Events are inserted into the queue upon scheduling, and the simulation advances by repeatedly extracting the minimum-priority event (the earliest timestamp) for processing, which may generate new future events to be inserted. This approach ensures events are handled in chronological order, enabling efficient modeling of systems like queueing networks in operations research. For instance, in simulating a bank teller system, customer arrival and service completion events are prioritized by time, allowing dynamic updates to the queue as the simulation progresses.[51] Priority queues also play a central role in best-first search algorithms, where nodes are prioritized based on an evaluation function estimating path cost. In the A* algorithm, a specific form of best-first search, the priority is determined by the function $ f(n) = g(n) + h(n) $, where $ g(n) $ is the exact cost from the start node to $ n $, and $ h(n) $ is a heuristic estimate of the cost from $ n $ to the goal; the algorithm uses extract-min operations on these $ f $-scores to expand the most promising node next. Introduced in 1968, A* guarantees optimality if $ h $ is admissible (never overestimates the true cost).[52] A practical example of A* is pathfinding in grid-based environments, such as robotics or video games, where the open set is maintained as a priority queue of nodes ordered by $ f $-score, typically storing tuples like $ (f, g, \text{node}) $ to break ties and avoid exhaustive exploration as in breadth-first or depth-first search. This directed expansion reduces the search space significantly compared to uninformed methods, making it suitable for large graphs. In graph representations, A* with a binary heap priority queue achieves a time complexity of $ O(E \log V) $, where $ E $ is the number of edges and $ V $ is the number of vertices, assuming consistent heuristics.[52][53] Priority queues have been applied in computer graphics for adaptive terrain rendering, as in the ROAM (Real-time Optimally Adapting Meshes) algorithm, which uses dual priority queues to manage split and merge operations on triangular meshes based on screen-space error metrics. Triangles exceeding an error threshold are prioritized for refinement in the split queue, while opportunities for coarsening are handled in the merge queue, enabling real-time updates for large-scale terrains viewed from varying distances. Developed in 1997, ROAM ensures frame-coherent rendering by processing high-priority updates first.[54]

Other practical uses

Priority queues play a crucial role in bandwidth management within computer networks, particularly for packet scheduling based on Quality of Service (QoS) levels. In routers and switches, they enable the classification and prioritization of traffic streams, ensuring that high-priority packets, such as those for voice or video, are transmitted ahead of lower-priority data. For instance, Weighted Fair Queuing (WFQ) employs a priority queue mechanism to approximate Generalized Processor Sharing (GPS), allocating bandwidth fairly according to assigned weights while preventing starvation of lower-priority flows.[55][56] In operating systems, priority queues facilitate task scheduling by ordering processes based on their priority levels to optimize resource allocation and responsiveness. The Linux Completely Fair Scheduler (CFS), introduced in kernel version 2.6.23, uses a red-black tree structure as an efficient implementation of a priority queue, where virtual runtime serves as the key for ordering tasks to ensure fair CPU time distribution across processes.[57] This approach allows for O(log n) insertion and minimum extraction operations, making it suitable for dynamic workloads in multi-tasking environments.[58] Priority queues are integral to numerical computations, such as nearest neighbor searches using k-d trees, where they manage the exploration of spatial partitions by prioritizing nodes based on distance bounds to the query point. In the Best-Bin-First (BBF) search algorithm for k-d trees, a priority queue orders candidate regions by their potential to contain nearer neighbors, improving efficiency in high-dimensional data retrieval tasks common in computer graphics and machine learning.[59] Similarly, in Monte Carlo simulations for molecular dynamics, priority queues schedule collision events by predicted timestamps, enabling event-driven processing that scales to large particle systems without exhaustive pairwise checks.[60] In machine learning, particularly reinforcement learning, priority queues enhance training efficiency through prioritized experience replay. The 2015 Prioritized Experience Replay method, integrated with Deep Q-Networks (DQN), uses a sum-tree-based priority queue to sample transitions from the replay buffer based on their temporal-difference error, focusing on more informative experiences to accelerate learning and improve policy convergence.[61] An practical example of priority queues in networking is their use in routers for congestion management, where low-priority packets are dropped first during buffer overflows to protect critical traffic. In Priority Queuing schemes, incoming packets are directed to separate queues by priority, and when queues exceed limits, the router selectively discards from the lowest-priority queue, maintaining throughput for high-priority flows like real-time applications.[62][63]

Advanced Variants

Parallel and concurrent priority queues

Parallel and concurrent priority queues extend the standard insert and extract-min operations to support multi-threaded access in shared-memory multiprocessor environments, addressing synchronization challenges such as contention and race conditions to ensure linearizability and scalability. These structures are essential for applications requiring dynamic prioritization under high concurrency, like task scheduling and parallel graph algorithms. Early designs in the 1980s and 1990s laid the foundation by adapting heap-based implementations for concurrent operations, while modern variants incorporate lock-free techniques and parallel batching for better performance on multicore and many-core systems. Recent advances as of 2025 include the Multi Bucket Queue (MBQ), which targets efficient concurrent priority scheduling by distributing tasks across multiple buckets to reduce contention and improve throughput in high-thread-count scenarios.[64][65] Concurrent access mechanisms primarily fall into lock-based and lock-free categories. Lock-based approaches, such as fine-grained locking on individual heap nodes, minimize contention by acquiring locks only on affected paths during operations; for instance, the algorithm by Hunt et al. (1996) uses multiple per-node locks and a bit-reversal technique for bottom-up insertions to scatter accesses across the heap fringe, reducing hot-spot formation while supporting top-down deletions. This method avoids deadlocks through careful lock ordering and demonstrates improved throughput on small to large heaps under mixed workloads on shared-memory multiprocessors like the Silicon Graphics Challenge. In contrast, lock-free implementations rely on atomic compare-and-swap (CAS) operations to achieve progress without blocking, as in the skiplist-based priority queue by Sundell and Tsigas (2003), which supports fully concurrent inserts and delete-mins suitable for pre-emptive multi-threaded systems. These lock-free designs outperform lock-based ones for three or more threads across various platforms, including up to 64-processor SGI Origin 2000 systems, by eliminating priority inversion and deadlocks; however, they must mitigate issues like the ABA problem—where a reused memory location fools a CAS—through techniques such as pointer marking with deletion bits.[66][67] Models for these queues typically operate in shared-memory settings with synchronization barriers to coordinate multi-processor access, as explored in 1990s work adapting array-based heaps for concurrent use. Building on foundational concurrent operations from Jones (1989), which enabled pipelined insertions and deletions on skew heaps, later algorithms like Hunt et al. (1996) further optimized for parallelism by allowing up to O(M) concurrent operations on a heap of size M, compared to O(log M) in prior sequential adaptations. Parallel operations often involve batch inserts and extracts to amortize overhead, using strategies like work-stealing in partitioned or multi-queue setups; for example, each thread maintains a local priority queue, inserting elements randomly and stealing high-priority tasks from others' queues when idle, which balances load dynamically in task-parallel schedulers. Partitioned heaps distribute elements across multiple sub-heaps to localize contention, facilitating efficient batch processing in shared-memory models.[66][68] Performance metrics emphasize scalability to p processors, ideally achieving O((log n)/p) time per operation through reduced contention; the funnel-tree algorithm by Shavit and Zemach (1999) exemplifies this by scaling near-linearly to 128 processors on simulated shared-memory architectures, outperforming simple tree-based designs by up to 5x at high concurrency levels with more than 10 priority ranges. In practice, lock-free skiplist queues like Sundell and Tsigas (2003) show superior throughput under pre-emptive scheduling compared to fine-grained lock-based heaps. Gaps persist in highly parallel domains, such as post-2010 GPU ray-tracing, where priority queues must handle massive thread counts; implementations like the parallel heap by He et al. (2012) use wide nodes and SIMT execution on CUDA-enabled GPUs, yielding up to 30-fold speedups over optimized sequential or multicore versions for fine-grained dynamic scenes in ray-tracing and photon mapping. Some GPU approaches approximate priority queues via sorting networks for ray reordering, enhancing traversal efficiency in real-time rendering pipelines.[65][67][69][70]

Fibonacci and pairing heaps

Fibonacci heaps represent an advanced sequential priority queue variant designed to achieve optimal amortized time bounds for key operations, extending ideas from binary heaps but with more flexible tree structures. Recent theoretical work as of 2025 includes strict Fibonacci heaps, which provide pointer-based implementations matching the amortized bounds in the worst case, addressing long-standing gaps in deterministic performance guarantees.[71] Each tree in a Fibonacci heap is represented using circular doubly-linked lists for both the root list and the children of each node, allowing efficient splicing and linking without immediate restructuring.[35] This lazy merging approach delays consolidation of trees until necessary, typically during a delete-min operation, which helps maintain low amortized costs.[35] The amortized analysis of Fibonacci heaps relies on a potential function defined as $ \Phi(H) = t(n) + 2m(n) $, where $ t(n) $ is the number of trees in the heap and $ m(n) $ is the number of marked non-root nodes, capturing the "debt" from lazy operations to bound future costs.[35] Core operations include linking two trees in constant time by attaching the root of one to the other based on key comparison, without changing ranks immediately.[35] Cutting a node from its parent takes amortized O(1) time, potentially triggering a cascading cut to unmarked ancestors if the node was marked, preserving the degree condition that limits tree heights.[35] Consolidation during delete-min merges trees of equal rank in O(log n) amortized time, ensuring the overall structure remains balanced over a sequence of operations.[35] Pairing heaps, introduced as a simpler alternative, use a self-adjusting tree structure with a child-sibling representation, where each node points to its first child and next sibling, enabling efficient splicing for merging two heaps in O(1) time by linking roots directly. Recent analyses as of 2023 have refined the efficiency of self-adjusting variants like multipass pairing heaps and smooth heaps, confirming their practical amortized performance under varied workloads.[72] The delete-min operation typically employs a two-pass approach: first pairing sibling subtrees left-to-right, then recombining them right-to-left in a weight-biased manner, where heavier subtrees are linked to lighter ones to promote balance.[73] One-pass variants, such as front-to-back or back-to-front splicing, simplify the process but lack complete theoretical bounds, though they perform comparably in practice.[73] Empirical studies confirm pairing heaps' practical efficiency, with the auxiliary two-pass variant showing the lowest work ratios—often below 2.0—in simulations involving up to 1.2 million operations mixing inserts, decrease-keys, and delete-mins, outperforming multipass variants under heavy decrease-key workloads.[74] Compared to Fibonacci heaps, pairing heaps achieve similar amortized bounds—O(1) for insert and decrease-key, O(log n) for delete-min—but with far less implementation complexity, using fewer pointers per node and avoiding explicit rank or mark fields.[73][74] Developed in the 1980s by Fredman, Sedgewick, Sleator, and Tarjan, pairing heaps excel in benchmarks due to their straightforward code and reduced overhead, often running faster than Fibonacci heaps despite the latter's stronger theoretical guarantees.[73][74] Both structures inherit flexibility from binary heaps but introduce amortization to handle decrease-key efficiently; however, they suffer from high constant factors in operations and poor cache locality due to scattered pointer traversals in their linked-list representations, limiting real-world adoption without optimizations.[75]

References

User Avatar
No comments yet.