|
Priority Queues
Abstract
The standard C++ library provides an adaptor to turn a container into
a priority queue. However, this priority queue misses two important
features, namely the possibility to change or remove an arbitrary
element and logical inspectability.
This component implements a collection of priority queues, all with
different trade offs.
Synopsis
|
#include "boost/heap.hpp"
namespace boost
{
template <typename T, typename Comp = std::less<T>, int d = 2>
class d_heap;
template <typename T, typename Comp = std::less<T> >
class fibonacci_heap;
template <typename T, typename Comp = std::less<T> >
class lazy_fibonacci_heap;
template <typename T, typename Comp = std::less<T> >
class pairing_heap;
template <typename T, typename Cont = std::vector<T>,
typename Comp = std::less<typename Cont::value_type> >
class priority_queue;
template <typename T, typename Cont = std::deque<T> >
class queue;
template <typename T, typename traits>
class radix_heap;
template <typename T, typename Comp = std::less<T> >
class splay_heap;
template <typename T, typename Cont = std::vector<T> >
class stack;
}
|
Description
The priority heaps provide efficient access to the "largest"
element where the definition of what is considered to be the largest
elements depends on the type of the priority queue:
-
For the class template boost::queue the largest elements is the
the element which is stored the longest time in the queue (similar
to boost::stack this is normally not considered a priority
queue).
-
For the class template boost::stack the largest element is the
most recently added element (it is kind of stretching the definition
of "priority queue" to consider this a priority queue but
this component is a reasonable home for this template...).
-
For the class template boost::radix_heap the element type is
assumed to provide access to a non-negative integral value describing
relative order of the element. This value is used to sort elements in
ascending order.
-
For all other types, a template argument is used to specify a
binary predicate which is used to determine an ordering on the
elements of the heap.
All types of priority queues provide a common interface which is made
up of a few operations and typedefs:
-
value_type
-
The type of the elements stored in the priority queue.
-
size_type
-
The type used to maintain the number of elements in the
priority queue.
-
const_reference
-
The type returned from top().
-
const_iterator
-
The type of the iterator used to iterate over all elements
in the priority queue (it is called const_iterator rather
than iterator because the elements are immutable).
-
Default Constructor
-
Construct an empty priority queue (however, this
constructor is only available if the comparator
type provides a default constructor).
-
push()
-
Add an element to the priority queue.
-
top()
-
Provides access the current largest element.
-
pop()
-
Remove the current largest element from the priority queue.
-
size()
-
Returns the number of elements in the priority queue.
-
empty()
-
Returns whether there are elements in the priority queue.
-
begin()
-
Returns an iterator to the first element of the priority queue.
-
end()
-
Returns a past the end iterator for the priority queue.
Note, that all classes have this common interface. This is
unfortunately not the case with the standard version of the adaptor
std::queue which does not provide the function top():
Instead, this adaptor provides the functions front() and
back() (which are, of course, also provided for the Boost
version).
The description will always assume that there is efficient access to
the "largest" element of a priority queue (this is in contrast to
all literature I have which always uses the "smallest" element but
consistent to the behavior of the default for the standard adaptor
std::priority_queue). Of course, for all priority queue
templates which take a comparator as template argument, this can be
changed easily. For example to provide access to the smallest element
just use std::greater<T> instead of std::less<T> (both
templates are defined in the header <utility>) as the
type of the comparator. For the templates boost::queue
and boost::stack the largest element is determined by the
insertion order (the first element added and the last element added,
respectively). Only Radix heaps somewhat rely on the fact that
efficient access is indeed to the smallest integer in the priority
queue. To make this class consistent with the other types, an ugly
hack is used (see the documentation of
Radix heap for details).
In any case, if the documentation mentions the largest element, it is
to be viewed with this intention in mind: Using the default parameters
for the priority queue templates will provide efficient access to the
largest element at least for the built-in types. For other types than
built-in types, you have to provide an appropriate comparator (or
define operator<()) which defines an order on the values.
Note that I omitted the details on what kind of order: I'm currently
not sure about the exact kind of order required for the various
priority queues to work. It definitely works for total orders...
Several priority queue implementions use some sort of tree internally
where a simple invariant is maintained: The data stored with a nodes
is larger than the data of all child nodes. This way the root of every
subtree always stores the largest element in this subtree. The
different implementations either differ in the details how this
invariant is maintained or what kind of accesses are allowed
(the template boost::priority_queue basically implements
a 2-heap but unlike the template boost::d_heap no methods
for manipulation of elements different than the top element are
provided). This basically applies to all implementations except for
Radix heaps, although boost::queue and boost::stack
maintain degenerate trees: In both cases it is just a sequence.
Properties of the Different Classes
This section is intended to give you an overview of all available
priority queues to determine which is the best suitable for your
situations. The table lists the supported operations, support for
arbitrary comparators, and the efficiency of the implementation. The
operations are put into groups:
-
Basic
-
Represents basic the operations
push(),
top(),
pop(),
size(),
empty()
-
begin()
-
Represents support for
logical inspectability, i.e.
an iterator type and the methods
begin(),
end()
-
change_top()
-
Represents self, that is support for the method
change_top()
-
change()
-
Represents the methods changing the priority of an
arbitrary element, i.e. the methods
change(),
decrease(),
increase()
The efficiency mentioned tries to describe the efficiency of the implementation.
However, until now I have only made some very simple performance tests using
only random data. For a really good estimate on the performance of
the various implementations, it is necessary to measure the performance on
real problem data.
The last six classes differ in their asymptotical behavior (which is important
to theoreticians but probably completely pointless for the practioneer as they
are all theoretical quite close). However, measuring different scenarios
(using random data...) indicated that each of the classes has its strength
depending on the number of elements and the mix of operations used. If your
application normally uses a common mix of operations you might be able to
enhance the performance by just testing which of the priority queues works
best for your application.
Bugs
There are currently some known bugs which mainly stem from the fact that I
want to get this stuff at least temporarily off my desk:
-
The implementation of the Radix heap is not
yet complete. I'm pretty sure that it can be done but I haven't figured
out some of the details yet and the literature I have is a little bit
terse on some of the more intersting parts. However, it seems that this
priority is relatively fast where it is applicable. Thus, it might be
interesting to use it.
-
I haven't cared much about exception safety. I think it should be doable
to add exception safety since most classes are "node based": after
construction of an element, only built-in types are manipulated (notably
boost::priority_queue seems to be
the biggest problem with respect to exception safety). However, the
compare function might throw exceptions
and it is not always obvious how to restore the data structure if
the comparison of objects is unreliable.
-
This documentation is probably full of typos, grammatical errors,
and, hopefully to a much lesser degree, technical inconsistencies...
See Also
d_heap(3),
f_heap(3),
l_heap(3),
p_heap(3),
p_queue(3),
queue(3),
r_heap(3),
s_heap(3)
stack(3)
Copyright © 1999 Dietmar Kühl (dietmar.kuehl@claas-solutions.de)
Claas Solutions GmbH
|