Priority Queues

Steven J. Zeil

Last modified: Oct 26, 2023
Contents:

Problem: Given a collection of elements that carry a numeric “score”, find and remove the element with the smallest [largest] score. New elements may be added at any time.

This collection is called a priority queue because,

1 The std Priority Queue Interface

This is the entire priority queue interface.

template <class T, class Container, class Compare> 
class  priority_queue 
{
public:
  typedef T value_type;
  typedef Container::size_type size_type;

protected:
  Container c;
  Compare comp;

public:
  priority_queue(const Compare& x = Compare());

  template <class Iterator>
  priority_queue(Iterator first,
                 Iterator last, 
                 const Compare& x = Compare());

  bool empty() const;

  size_type size() const;

  value_type& top();

  const value_type& top() const;

  void push(const value_type& x);

  void pop();
};


1.1 priority_queue is an Adapter

template <class T, class Container, class Compare> 
class  priority_queue 
{
public:
   ⋮

Like std::stack and std::queue, priority_queue is an adapter that works on an underlying sequential container, which must provide random-access iterators (vector or deque). For example,

priority_queue<Event, vector<Event> > myPQueue;

2 Applications of Priority Queues

Priority queues tend to crop up in a lot of algorithms as “helpers” for working with other data structures. In particular, we’ll see them in connection with a lot of algorithms for finding optimal paths and doing other work with graphs.

Priority queues are often useful in scheduling. For example, in discrete event simulation, we simulate real-world systems as a series of events that are scheduled to take place at some future time. For example, if we wanted to simulate automobile traffic on downtown streets, we might create a whole collection of events, some of which spawn other future events:

All scheduled events could be kept in a (min) priority queue ordered on the time at which the event is scheduled. The main logic of the simulation is simply

priority_queue<Event, vector<Event> > futureEvents;
   ⋮
while (simulation has not ended)
{
   get next event from futureEvents;
   trigger that event;
}

Each distinct type of event would have its own “trigger” function, many of which are likely to add one or more additional events to the priority queue. What’s essential is that we always be able to get to the next scheduled event each time around that main loop. When a new event gets added, it might stay far down in the queue (if the new event is a long time in the future) or might spring to the front of the queue if there is nothing more immediate. For example, suppose we have a single car speeding down Main Street at 3AM. Each of its “will-reach-next-intersection” events is likely to go immediately to the first position in the queue, unless the traffic lights are cycling very quickly.