The syntax to implement min-heap using priority_queue STL is
std::priority_queue<int, std::vector<int>, std::greater<int> > my_min_heap;
whereas, the syntax of implementing a sort() for vector in descending order is
sort(a.begin(), a.end(), greater<int>());
My question is sort uses () after greater but priority_queue does not. Why is that?
Here
std::priority_queue<int, std::vector<int>, std::greater<int> > my_min_heap;
std::greater<int> is a template parameter, note that it is inside <>. It is a type. On the other hand, here
sort(a.begin(), a.end(), greater<int>());
an instance of that type is created and passed to sort. Also sort has a template parameter for the comparator, but it is deduced from the argument (to be of type std::greater<int>), so there is no need to specify it explicitly.
More specifically std::greater<int> is a functor ie a type that has an overload for the function call operator. To create an instance and use that to compare two ints you could write:
bool x = std::greater<int>()(5,3);
/* the type */
// ^ call constructor
// ^ call the objects operator()
However, typically the same function object is reused for many comparisons, as is the case with std::sort.
Why is it that when priority_queue is used with a single data type, like 'int', we initialise it like this: priority_queue<int>; but, when it's initialized with a pair we add a second parameter of type vector priority_queue<pair<int,int>, vector<pair<int,int>>>?
Also, I've noticed several ways to add a third argument that specifies ordering.
Method 1 - Struct
struct myCompare {
bool operator()(const pair<int, int>& A, const pair<int, int>& B) {
return A.second < B.second;
}
};
priority_queue<pair<int, int>, vector<pair<int, int>>, myCompare> leaderBoard;
Method 2 - Lambda
auto myComp = [](const pair<int, int>& A, const pair<int, int>& B)
{return A.second < B.second;};
priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(myComp)> leaderBoard(myComp);
My Questions
Why is the second parameter of priority_queue a vector? What does this mean and when does this second parameter need to be specified?
In method 2, why is decltype needed with this lambda?
In method 2, why does the object leaderBoard need to be initialised with (myComp)
In method 2, why can I not specify my lambda as the third argument directly?
What is the difference between A.second > B.second and A.second < B.second? How do you remember which one means ascending order, and which one is descending order?
Why is it that when priority_queue is used with a single data type, like 'int', we initialise it like this: priority_queue<int> [...] ?
Because std::priority_queue is a class template defined as follows:
template<
class T,
class Container = std::vector<T>,
class Compare = std::less<typename Container::value_type>
> class priority_queue;
As you can see, you can instantiate this class only providing the T, because the rest of the types will be defaulted. You can read more about template default arguments here.
but, when it's initialized with a pair we add a second parameter of type vector priority_queue<pair<int,int>, vector<pair<int,int>>>?
Because someone wanted to be explicit. priority_queue<pair<int, int>> is equivalent to priority_queue<pair<int,int>, vector<pair<int,int>>>, because the second template type (vector<pair<int, int>>) will be present by default.
Why is the second parameter of priority_queue a vector? What does this mean and when does this second parameter need to be specified?
We don't need to specify it explicitly. The second template parameter is a type use for internal representation of data. std::priority_queue is a container adaptor, which means that it's not a container by itself - it uses some other container and wraps it with certain kind of utilities.
In method 2, why is decltype needed with this lambda?
Because you need to provide a type. myCompare is a struct, so it's a name of a type. myComp is not a type, it's a variable. You wish to get its declared type? Use decltype.
In method 2, why does the object leaderBoard need to be initialised with (myComp)?
Because you cannot default construct an object given a decltype of a lambda (this got relaxed in C++20). You need to use the following constructor:
explicit priority_queue(const Compare& compare)
: priority_queue(compare, Container()) { }
which expects a callable (in this case - the lambda) that will be used as a comparator.
In method 2, why can I not specify my lambda as the third argument directly?
You mean as the third template argument? Because as of right now, lambdas cannot be used in unevaluated contexts, and providing a type for a template is one of those.
5.1. What is the difference between A.second > B.second and A.second < B.second?
The difference is quite blatant. One checks for A.second being greater than the second argument and the other one is the reverse. It is commonly used for sorting (for comparing elements).
5.2 How do you remember which one means ascending order, and which one is descending order?
It's pretty easy - the C++ conception is to use < between the left hand side argument and the right hand side argument, like so: left_hand_side < right_hand_side.
In method 2, why does the object leaderBoard need to be initialised
with (myComp)
it depends on what C++ standard you use. Before C++20 you need to pass functor created by lambda expression to priority_queue constructor because lambdas until C++20 were not default constructible.
Since C++20 you can write:
priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(myComp)> leaderBoard;
it will work fine because compiler knows type of lambda - specified by decltype(myComp), and it can call its default ctor.
In method 2, why can I not specify my lambda as the third argument
directly?
Yes, you can but you need to still use decltype to get comparator type:
priority_queue<pair<int, int>, vector<pair<int, int>>,
decltype( [](const pair<int, int>& A, const pair<int, int>& B)
{return A.second < B.second;} ) > leaderBoard;
Why is the second parameter of priority_queue a vector?
You can use any container that satisfies certain requirements. vector is a reasonable default choice.
What does this mean and when does this second parameter need to be specified?
If either you want to change its default value, or you want to specify the third one. std::priority_queue<std::pair<int,int>> is a valid type without specifying the second paramater.
In method 2, why is decltype needed with this lambda?
If you want to use a custom comparator, you have to specify its type as a third template parameter. Each lambda has its own unique type, the only way to obtain it, is through decltype.
In method 2, why does the object leaderBoard need to be initialised with (myComp)
Because struct-based comparator can be default constructed, but lambda-based one can't. The relevant constructors are
priority_queue() : priority_queue(Compare(), Container()) { }
priority_queue(const Compare& compare) : priority_queue(compare, Container()) { }
If you don't provide compare, Compare() is used. myCompare() is a valid value, and decltype(myComp)() is not.
In method 2, why can I not specify my lambda as the third argument directly?
You need a type, not a value of that type.
What is the difference between A.second > B.second and A.second < B.second?
Swapping the order of arguments (or, equivalently, < and >) turns a min-heap into a max-heap and vice versa, i.e. that order determines which element will be returned by .top(), the smallest or the largest.
Whenever I want to create a min-heap using a priority queue (which creates a max-heap by default),
I need to pass a comparator and also a vector of the required type to be sorted, something like this:
std::priority_queue<int, std::vector<int>, std::greater<int> > pq;
Why do we do this? And why do we not have to the same for a max-heap implementation?
Because of template parameters being positional and C++ standard commitee's decision to order the containter type before the comparator type.
Just like with a function like this:
void foo(int a = 1, int b = 2);
- you can't call it specifying b, but not a.
For max-heap, you're using std::less<int>, which happens to be the default, therefore you can omit the container type as well.
template <class T, class Container = vector<T>, class Compare = less<typename Container::value_type> > class priority_queue;
I understand the first two template arguments, the first template argument is the data type of the element being stored in the priority queue and the second one is the kind of container that the programmer wants to use, it could be either deque or vector.
But the third argument confuses me a bit, because I have never seen something like it. I'd have done something like:
template <class T, class Container = vector<T>
class priority_queue{
/* Implementation */
};
Does it have something to do with the strict weak ordering criterion necessary for priority queue? If yes, how can I learn more about it? Could you give an example of using the third argument?
I am new to template programming, so I'd really appreciate your help.
The third parameter specifies the comparator class.
The comparator class is responsible for comparing queue elements, in order to determine the queue order. You already understand that the elements in the queue are ordered with the "higher" values first. Well, this is what defines what "higher" means, here.
The comparator class has a simple interface: given two values, return true if the first value is less than the second value, and false otherwise. The default implementation, std::less, uses the traditional < operator to compare the two values.
Use a custom comparator class in order to change the behavior of the priority queue. One example would be to specify std::greater instead of std::less as the comparator class. std::greater uses the > operator, so this creates a priority queue "in opposite order", which gives you the lowest values first, rather than highest one.
Or, you could create your own custom comparator class, such as:
class last_four_bits {
public:
bool operator()(int a, int b) const
{
return (a & 0x0F) < (b & 0x0F);
}
};
This comparator class compares the least four bits of an int only. This, in turn, makes this:
std::priority_queue<int, std::vector<int>, last_four_bits>
look at the least four bits of each int value in the queue, thus ordering all ints with the highest values in the last four bits before the ones with the lesser values, ignoring all other bits in the int.
P.S. Comparator classes are also used with associative containers, sets and maps, and serve the same function there. By carefully crafting a comparator class you can create sets and maps whose iterators iterate over the keys in the set/map in some order other than the lowest to the highest keys (as you understand "lowest" and "highest" to mean, intrinsically).
Why priority queue have that signature ?
std::priority_queue<int, std::vector<int>, std::greater<int> > third;
For what need std::vector<int>? If I, for example, need to store in queue only ints ?
These are the template arguments for priority_queue. The second one is the backing container used for storing the values and in this case you use vector (which is the default container). In the example above it is passed so that you can change the default comparison predicate with std::greater(i.e. have a priority_queue where the smallest value is at the top). Have a look at the class declaration here.