I have a priority queue of pointers to a struct city
. I modify the objects pointed by these pointers outside the priority queue, and want to tell the priority queue to "reorder" itself according to the new values.
What should I do?
Example:
#include <iostream>
#include <queue>
using namespace std;
struct city {
int data;
city *previous;
};
struct Compare {
bool operator() ( city *lhs, city *rhs )
{
return ( ( lhs -> data ) >= ( rhs -> data ) );
}
};
typedef priority_queue< city *, vector< city * >, Compare > pqueue;
int main()
{
pqueue cities;
city *city1 = new city;
city1 -> data = 5;
city1 -> previous = NULL;
cities.push( city1 );
city *city2 = new city;
city2 -> data = 3;
city2 -> previous = NULL;
cities.push( city2 );
city1 -> data = 2;
// Now how do I tell my priority_queue to reorder itself so that city1 is at the top now?
cout << ( cities.top() -> data ) << "\n";
// 3 is printed :(
return 0;
}
This is an old question but I wasn't fully satisfied with any of the answers when I wanted to do this myself. There is no need for any hacks.
std::priority_queue
contains all the machinery to do this legally and idiomatically.std::priority_queue
has two very helpful data members,c
(the underlying container) andcomp
(the comparison predicate).Equally helpfully, the standard mandates that the
Container
template type must be a model ofSequenceContainer
who's iterators are models ofRandomAccessIterator
.This is helpful because the
Iter
argument type ofstd::make_heap
have the sameRandomAccessIterator
model requirement.This is a longwinded way of saying that
std::priority_queue
is a wrapper around a heap and that thereforestd::make_heap(std::begin(c), std::end(c), comp)
must be a valid expression.The 'bad' news is that
c
andcomp
are protected. This is actually good news for two reasons:You can't destroy the heap accidentally.
If you derive from
std::priority_queue
you can modify the heap intentionally.So the trick is to derive your priority queue from
std::priority_queue
, in a member function, mutate the internal heapc
any way you like and then callstd::make_heap(std::begin(c), std::end(c), comp);
to turn it back into a valid heap.Well, yes, but...
There are two reasons that this could be a bad idea for the young and/or unwary. Lack of polymorphic destructors and the risk of slicing.
There is actually no reasonable use case for owning a std container through a pointer to its base class. Containers are lightweight (when there is nothing in them) and cheaply moveable. You may be able to think of use cases, but I can guarantee that whatever you intended to do can be done better by encapsulating the container in another heap-allocated object. In well-designed code, this should never have become a concern. In any case, inheriting privately from the
priority_queue
template class removes this risk.Certainly there is a risk of slicing when we pass inherited objects around. The answer here is to inherit privately from the
priority_queue
base class, and then useusing
in the derived class to export only the parts of the base class's interface that we wish to share.The example below has been updated to show this.
Below is an example from a real project.
Requirements: Keep a queue of topics that a client must be notified changes to. Order the queue by the timestamp of the earliest time that this topic was notified. Do not allow duplicate topic names.
Here is a working demo:
Based on http://www.sgi.com/tech/stl/priority_queue.html it does not look like there is a way to do that, without emptying and re-inserting.
If you are willing to move away from priority_queue (but still want a heap), then you can use a vector, along with the make_heap, push_heap and pop_heap. See the Notes section in the page for priority_queue.
This is a bit hackish, but nothing illegal about it, and it gets the job done.
Update:
Do not use this hack if:
vector
.Compare
functor has behavior that would cause your external copy to order differently than the copy ofCompare
stored inside thepriority_queue
.You can always write your own container adaptor which wraps the heap algorithms.
priority_queue
is nothing but a simple wrapper aroundmake/push/pop_heap
.If you need to keep an ordered collection you may consider the following solution: Use
std::set
and to update values remove the item, update its value and place it back into the set. This will give you O(log n) complexity for updating one item, which is the best you can in a usual heap structure (Assuming you had access to its internals to mass with the sift-up sift-down procedures). The only downside tostd::set
will be the time for initializing a set with n items. (O(n log n) instead of O(n)).The stl's containers don't provide as fast as possible updatable priority queues.
@Richard Hodges:
make_heap
takes O(n) complexity, and thepush_heap
function doesn't tell you where the provided element was stored, making a quick update of a single element impossible (you need O(n) to find it).I have implemented a high-performance updatable priority queue (an update costs O(log n), twice as fast as using an
std::set
) and made it available on github.This is how you would typically use it :