Standard Library[lib]

C++ priority queue: std::priority_queue explained

Section
Standard Library [lib]
Standard
C++17
Compilers
MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2, Clang 23.1 (libc++)
Published
Length
7 min read
In this answer6 clauses
  1. 1How do you make a min-heap priority queue?
  2. 2Why is the comparator backwards?
  3. 3How do you write a priority queue comparator for a struct?
  4. 4How does std::priority_queue use a heap?
  5. 5What can’t a priority queue do?
  6. 6priority_queue, std::set or a sorted vector?

Abstract

std::priority_queue, from <queue>, hands back its largest element first: push() adds an element, top() reads the largest and pop() removes it, returning nothing. Pass std::greater as the third template argument for the smallest first, or a comparison of one field for a struct. Underneath, by default, is a std::vector kept in heap order, so push() and pop() take logarithmic time and top() constant time. Check empty() first: top() or pop() on an empty queue is undefined behavior.

A binary tree of seven circles joined by lines. The circle at the top is the largest and solid blue; every hatched circle below it is smaller than the circle it hangs from.
Figure 1 — The largest element is always on top.

A C++ priority queue, std::priority_queue from the header <queue>, is a container that always gives you its largest element first: push() adds an element, top() reads the largest, pop() removes it. push() and pop() take logarithmic time and top() constant time. For the smallest first, give it std::greater<> as the comparison.

Four numbers in, the largest out first:

[Example 1:
#include <iostream>
#include <queue>

int main() {
    std::priority_queue<int> pq;
    pq.push(3);
    pq.push(9);
    pq.push(4);
    pq.push(7);

    std::cout << "top: " << pq.top() << '\n';   // top: 9

    while (!pq.empty()) {
        std::cout << pq.top() << '\n';           // 9, 7, 4, 3
        pq.pop();
    }
}

— end example]

It printed:

[Example 2:
top: 9
9
7
4
3

— end example]

The answer is written for C++17 (Since When’s C++17 table). The samples were built with MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2 and Clang 23.1 (libc++), and all four printed the same unless a sentence says otherwise. A std::queue, from the same header, hands elements back in the order they arrived; a priority queue hands back the highest-ranked one, which is what a task scheduler, Dijkstra’s shortest paths or a top-k search needs.

How do you make a min-heap priority queue?

Pass std::greater<int> as the third template argument: std::priority_queue<int, std::vector<int>, std::greater<int>> hands back the smallest element first. That makes it a min-heap, a heap with the smallest element on top. The same four numbers:

[Example 3:
std::priority_queue<int, std::vector<int>, std::greater<int>> pq;
pq.push(3);
pq.push(9);
pq.push(4);
pq.push(7);

while (!pq.empty()) {
    std::cout << pq.top() << '\n';           // 3, 4, 7, 9
    pq.pop();
}

— end example]

All four printed 3, 4, 7 and 9. The second template argument is the container the queue keeps its elements in, std::vector<int> by default; you have to write it out to reach the third. std::greater<>, without the int, works the same.

Why is the comparator backwards?

Because the comparison answers “does a come out after b?”, not “does a come first?”. std::less, the default, says yes when a is smaller, so the largest comes out first; std::greater says yes when a is larger, so the smallest does. std::sort reads the same std::less from the other end and puts the smallest first (cppreference’s std::priority_queue page: the top is the “last” element in the comparison’s order).

How do you write a priority queue comparator for a struct?

Give the queue a comparison of the field you rank by, as its third template argument. With a lambda, decltype(by_priority) names the lambda’s type, which only the compiler can spell, and the lambda itself goes to the constructor:

[Example 4:
struct Task {
    int priority;
    std::string name;
};
// ...
auto by_priority = [](const Task& a, const Task& b) {
    return a.priority < b.priority;     // does a come out after b?
};
std::priority_queue<Task, std::vector<Task>, decltype(by_priority)>
    tasks(by_priority);

tasks.push({2, "write tests"});
tasks.push({5, "fix the crash"});
tasks.push({1, "update docs"});

while (!tasks.empty()) {
    const Task& next = tasks.top();
    std::cout << next.priority << ' ' << next.name << '\n';
    tasks.pop();
}

— end example]

All four printed 5 fix the crash, 2 write tests and 1 update docs: the highest priority first. Flip the comparison to a.priority > b.priority and the lowest priority comes out first. A small function object does the same job, and the queue needs no argument to build one:

[Example 5:
struct ByPriority {
    bool operator()(const Task& a, const Task& b) const {
        return a.priority < b.priority;
    }
};
// ...
std::priority_queue<Task, std::vector<Task>, ByPriority> queue;
queue.push({2, "write tests"});
queue.push({5, "fix the crash"});
std::cout << queue.top().name << '\n';  // fix the crash

— end example]

Either way the comparison must be a strict weak ordering, a consistent “less than”: the same rule as a comparator for std::sort and std::map. The standard promises no order for elements that compare equal: with five tasks of priority 2, the three standard libraries did not all agree on the order, and none kept the push order. To keep equal priorities in the order they were pushed, add a counter to Task that rises with every push, and when two priorities are equal return a.seq > b.seq.

Under C++17, tasks.emplace(5, "fix the crash") does not compile, because Task has no constructor that takes two arguments; C++20’s parenthesized aggregate initialization makes it build. push({...}), as above, works under both.

How does std::priority_queue use a heap?

A std::priority_queue is a container adaptor, a wrapper that lets you reach only part of the container inside it, and by default that container is a std::vector kept in heap order. A heap is a binary tree stored level by level in the vector: every parent is at least as large as its children, so the largest sits at index 0, and the children of index i sit at 2i+1 and 2i+2. After 3, 9, 4 and 7 go in, the vector holds 9 7 4 3 on all three standard libraries.

Top: a tree, 9 at the root, 7 and 4 below it, 3 under the 7. Bottom: vector cells 9, 7, 4, 3 at indexes 0 to 3; arrows join each parent cell to its children; cell 0 is top().
Figure 2 — A priority queue keeps a vector in heap order: each parent is at least as large as its children, so the largest sits at index 0.

The standard ([priority.queue]) defines push() as push_back() followed by std::push_heap, which sifts the new element up. It defines pop() as std::pop_heap, which swaps the top to the back and sifts the new top down, followed by pop_back(). The same algorithms, from <algorithm>, work on any vector (print writes it on one line):

[Example 6:
std::vector<int> v{3, 9, 4, 7};

std::make_heap(v.begin(), v.end());
print(v);                               // the largest at v[0]

v.push_back(8);
std::push_heap(v.begin(), v.end());     // sift the 8 up
print(v);

std::pop_heap(v.begin(), v.end());      // the largest to the back
print(v);
v.pop_back();
print(v);

— end example]

It printed:

[Example 7:
9 7 4 3
9 8 4 3 7
8 7 4 3 9
8 7 4 3

— end example]

The 8 rose to index 1 and the 7 moved down to index 4; then the old top, 9, went to the back for pop_back() to remove. All three libraries printed these layouts, but the standard promises only the heap property, not these exact positions.

To load data you already have, construct the queue from it: std::priority_queue<int> from_v(v.begin(), v.end()); runs std::make_heap once, at most 3N comparisons, instead of up to log N for each of N pushes. C++23 adds push_range() and a std::from_range constructor for the same job.

What can’t a priority queue do?

A priority queue gives you the top element and nothing else:

  • pop() returns nothing (void): read top() first.
  • top() and pop() on an empty queue are undefined behavior: the standard places no requirement on what the program then does. top() returns the vector’s front() and pop() ends with its pop_back(), and both are undefined on an empty vector. Check empty() first.
  • No iterators and no search: there is no begin(), no find() and no member function that reaches an element below the top.
  • No changing a priority: top() returns a const reference. Push a new entry and skip the old one when it reaches the top, or keep the data in a std::set, where you erase the old entry and insert the new one.

The safe loop checks empty(), reads, then pops:

[Example 8:
std::cout << pq.size() << " waiting\n";     // 4 waiting

while (!pq.empty()) {
    int largest = pq.top();     // read it first: pop() returns void
    pq.pop();
    std::cout << largest << '\n';           // 9, 7, 4, 3
}

std::cout << pq.size() << " waiting\n";     // 0 waiting

— end example]

All four printed 4 waiting, then 9, 7, 4 and 3, then 0 waiting.

priority_queue, std::set or a sorted vector?

Use a priority queue when the only question is “what comes next?”:

You need Use
Only the largest or smallest next std::priority_queue
To walk in order, or find or erase any element std::set or std::multiset
The data arrives once, then is only read a std::vector sorted once with std::sort

The other containers and adaptors are in Standard library.

Questions

Is std::priority_queue a max-heap or a min-heap?

A max-heap by default: with its default comparison, std::less, top() returns the largest element. Pass std::greater as the third template argument, as in std::priority_queue<int, std::vector<int>, std::greater<int>>, and top() returns the smallest instead.

Which header is std::priority_queue in?

<queue>, the same header that declares std::queue. A min-heap's declaration also names std::vector and std::greater, which come from <vector> and <functional>, so include those too.

What is the time complexity of push and pop in a priority queue?

push() and pop() take logarithmic time: the work grows with the logarithm of the number of elements. top() takes constant time. Building a queue of N elements at once, from a pair of iterators, takes a linear number of comparisons, at most 3N.

How do you iterate over a priority queue?

You cannot: std::priority_queue has no iterators, no begin() and no end(). Copy the queue and pop the copy until it is empty, which visits the elements in priority order (largest first by default), or keep the data in a std::set or a sorted std::vector if you need to loop over it.

Does pop() return the element in a C++ priority queue?

No. pop() returns void: it removes the top element and gives nothing back. Read top() first, into a variable if you need to keep it, then call pop().

Can a priority queue hold pairs?

Yes. A std::pair compares by first, then by second, so a priority queue of pairs puts the pair with the largest first on top and breaks ties by second. A pair of a priority and a name works without writing a struct or a comparator.

Sources

  1. std::priority_queuecppreference.com
  2. std::priority_queue::priority_queuecppreference.com
  3. std::priority_queue::topcppreference.com
  4. std::priority_queue::popcppreference.com
  5. std::make_heapcppreference.com
  6. std::vector::frontcppreference.com
  7. [priority.queue] Class template priority_queueC++ standard working draft
  8. [alg.heap.operations] Heap operationsC++ standard working draft

Read next

  1. C++ queue: how std::queue works, with examplesStandard Library · Miriam Castellanos · written for C++11
  2. Stack in C++: how std::stack works, with examplesStandard Library · Miriam Castellanos · written for C++17
  3. C++ std::find: search a vector, array or stringStandard Library · Miriam Castellanos · written for C++17