I am writing a class which has three priority queues as private members.
class Foo {
...
...
private:
// I am fine with using pointers instead if it helps.
std::priority_queue<int> first; // min heap.
std::priority_queue<int> second; // max heap.
std::priority_queue<int> third; // min heap.
};
Now I require first
and third
to start as min heaps
and second
as a max heap
. As part of the functionality of my class I need to do the following:
second
to first
. Ideally this is achieved through lowest amount of copying. The underlying vector should just be moved. Additionally first
should now behave like a max heap
.third
to second
. This means second
should now behave like a min heap
.third
's contents have been moved to second
, it should be empty. I would like to either allocate a new underlying vector or re-use first's
underlying vector (it doesn't need it any more. Additionally third should now be a max heap
.I need to perform this cycle (max -> min and min -> max) an unknown number of times.
I am struggling to do this with std::priority_queue
since the Comparator is a template argument which means I cannot change it at run time. This is preventing me from turning a min heap
into a max heap
.
So my questions are:
std::priority_queue
to do my bidding without making it extremely ugly?std::priority_queue
?heapify
logic in the std library to achieve this?Yes, in C++ priority_queue, we may have duplicate values.
priority_queue::swap() This function is used to swap the contents of one priority queue with another priority queue of same type and size.
Deletion in Priority Queue: As you know that in a max heap, the maximum element is the root node. And it will remove the element which has maximum priority first. Thus, you remove the root node from the queue. This removal creates an empty slot, which will be further filled with new insertion.
Is there a way I could possibly bend std::priority_queue to do my bidding without making it extremely ugly?
You could write a wrapper that hides the predicate and uses inheritance behind the scenes. However, that seems overkill.
If not then could I perhaps re-structure my class to do the same thing, but still use std::priority_queue?
You could wrap the access to the queues in functions. Then use a bool or integer variable to check which queue needs to be accessed.
Otherwise could I maybe re-use most of the heapify logic in the std library to achieve this?
This sounds like the best option, based on what you explained. Store each priority_queue
in a std::vector
and use the std::make_heap
, std::push_heap
and std::pop_heap
functions to manage the heap structure. If you keep all priority queues in a std::array<std::vector<int>, 3>
, you can use std::rotate
to perform the logic you described. In addition, you would need to keep a boolean variable indicating which predicate to use for the heap operations.
Actually, the STL provides facilities for exactly your case: You can pass a comparator to the constructor of the priority queue. The key idea is to give comparator some internal state which determines whether a less than or greater than operation should be applied. The comparator type looks like this:
struct LessOrGreater
{
explicit LessOrGreater( bool isLess ) : isLess{isLess} {}
bool operator()( int lhs, int rhs ) const
{
return isLess == (lhs<rhs);
}
bool isLess;
};
The actual type of the priority queue is
using MinOrMaxQueue =
std::priority_queue<int,std::vector<int>,LessOrGreater>;
Your class can now be implemented in terms of this special priority queue.
class Foo {
public:
Foo()
: first { LessOrGreater{ false } } // initialize as min heap
, second{ LessOrGreater{ true } } // initialize as max heap
, third { LessOrGreater{ false } } // initialize as min heap
{}
void op(); // The operation you explained
private:
MinOrMaxQueue first;
MinOrMaxQueue second;
MinOrMaxQueue third;
};
Now the operation you described could be implemented like this:
void Foo::op()
{
first = std::move(second);
second = std::move(third);
third = MinOrMaxQueue{ LessOrGreater{ true } }; // might throw!
}
However, this code is not exception-safe. Since the default constructor of std::vector<int>
might throw (the C++ standard does not guarantee no-fail here!) the third line of the op()
function could throw leaving the Foo
object in an invalid state. Here's an implementation that is strongly exception-safe and most likely just as efficient:
void Foo::op()
{
MinOrMaxQueue tmp{ LessOrGreater{ true } };
first .swap( second );
second.swap( third );
third .swap( tmp );
}
The first line is the only line that could possibly throw but it does not modify the Foo
object. So throwing does cannot possibly damage anything. The remaining three lines never throw and hence the function is strongly exception-safe.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With