1

I have a priority queue with 'keys' that currently looks like this:

using HeapKey = pair<Cost, Cost>;
vector<pair<HeapKey, unsigned int>> U;

I insert in the queue with this command:

push_heap(U.begin(), U.end(), KeyCompare());

And I have a sorting command like this:

struct KeyCompare {
  bool operator()(const pair<HeapKey, unsigned int>& a, const pair<HeapKey, unsigned int>& b) const
  {
    return a.first > b.first;
  }
};

I haven't worked with heaps very much, and my inexperience shows. I've read that heaps traditionally works by having the largest element first. I need the smallest. But I also need the queue to be sorted by lexicographical order so that a key is less than or equal to another key if:

(k1.first <= k2.first) OR (k1.first == k2.first AND k1.second <= k2.second)

And I wonder how exactly I code that in C++? Currently my code doesn't seem to work a 100% since mostly get the smallest element defined by the k1.first, but not always, and I need to implement that check of the second element if two keys are equal so I can sort my priority queue.

Thank you

Edit:

Sorry about the lack of code. Took me a little while but I think I have a good example with this:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

using Cost = float;
using HeapKey = pair<Cost, Cost>;
vector<pair<HeapKey, unsigned int>> U;

struct KeyCompare {
  bool operator()(const pair<HeapKey, unsigned int>& a, const pair<HeapKey, unsigned int>& b) const
  {
    return a.first > b.first;
  }
};

ostream& operator<<(ostream& os, pair<Cost, Cost> const& p) {
  return os << "<" << p.first << ", " << p.second << ">";
}
 
int main()
{
    U.push_back({ {5.62843, 2.8}, 1 });
    U.push_back({ {5.64264, 1.4}, 2 });
    U.push_back({ {6.01976, 1}, 3 });
    U.push_back({ {6.2, 5.2}, 4 });
    U.push_back({ {6.03607, 3.8}, 5 });
    U.push_back({ {6.03607, 3.8}, 6 });
    U.push_back({ {7.45028, 3.8}, 13 });
    U.push_back({ {7.45028, 3.8}, 14 });
    U.push_back({ {7.45028, 3.8}, 15 });
    U.push_back({ {5.62843, 1}, 16 });
    U.push_back({ {5.02843, 7.8}, 17 });
    push_heap(U.begin(), U.end(), KeyCompare());

    cout << "U: ";
    for (auto p : U) {
        cout << p.second << p.first << " - ";
    }
    cout << endl;

    for (int i = 0; i < 5; i++) {
        pop_heap(U.begin(), U.end(), KeyCompare());
        U.pop_back();
        cout << U.front().second << U.front().first << endl;
    }
}

So, what I get from this is:

U: 17<5.02843, 7.8> - 1<5.62843, 2.8> - 3<6.01976, 1> - 4<6.2, 5.2> - 2<5.64264, 1.4> - 6<6.03607, 3.8> - 13<7.45028, 3.8> - 14<7.45028, 3.8> - 15<7.45028, 3.8> - 16<5.62843, 1> - 5<6.03607, 3.8> - 
1<5.62843, 2.8>
2<5.64264, 1.4>
16<5.62843, 1>
3<6.01976, 1>
6<6.03607, 3.8>

And the problem I believe I'm having is, as you can see, that after I pop elements 17 I have element 1 as the next. However, element 16's first key is of equal size and it's second key is smaller, so I would like that to go first.

Furthermore element 2 comes before element 16 and 16's first key is smaller than element 2's first key, so that's not right.

Hope this is better.

OverDemon
  • 103
  • 1
  • 3
  • 8
  • 1
    `a.first.first < b.first.first || ( a.first.first == b.first.first && a.first.second <= b.first.second )` ... or `a.first < b.first` (assuming `a` and `b` are `std::pair, uint>` – ChrisMM Dec 06 '22 at 12:02
  • 3
    what is `pair<, unsigned int>` ? What is `Cost` ? Actually I dont understand how the title relates to the code in the quesiton. `std::pair` has an operator that does compare first then second – 463035818_is_not_an_ai Dec 06 '22 at 12:04
  • 2
    please post a [mcve] rather than just fragments of code – 463035818_is_not_an_ai Dec 06 '22 at 12:05
  • 3
    Fwiw, `<=` is almost always wrong in comparison functions that are to be used with standard algorithms/containers. – Ted Lyngmo Dec 06 '22 at 12:06
  • 1
    Relevant question regarding `<=` and strick weak ordering: https://stackoverflow.com/q/18291620/580083. – Daniel Langr Dec 06 '22 at 12:08
  • "less than or equal" is the wrong relation. The standard ordered containers rely on an "is ordered before" relation. `a.first < b.first` should work the way you want since the ordering for `std::pair` is lexicographic - if it doesn't, post a [mcve]. – molbdnilo Dec 06 '22 at 12:16
  • If you use a `std::priority_queue, unsigned int>>` without adding your own comparison, what does it do wrong? [Example](https://godbolt.org/z/qcKh7jjrG) – Ted Lyngmo Dec 06 '22 at 12:20

1 Answers1

1
  • You are using the wrong function (std::push_heap) to create a max heap. push_heap puts the last element in an already existing max heap in its correct place. Use std::make_heap to create the heap.
    std::make_heap(U.begin(), U.end(), KeyCompare());
    
  • The comparator needs to compare the second float before the first and possibly also the unsigned int to get the job done properly. To make that easy, use std::tie. If you for example want the smallest values extracted first:
    #include <tuple>
    
    struct KeyCompare {
        bool operator()(const std::pair<HeapKey, unsigned int>& a,
                        const std::pair<HeapKey, unsigned int>& b) const {
            return std::tie(a.first.second, a.first.first, a.second) >
                   std::tie(b.first.second, b.first.first, b.second);
        }
    };
    
    The above is the same as you would get if you instead did it manually like below:
    struct KeyCompare {
        bool operator()(const std::pair<HeapKey, unsigned int>& a,
                        const std::pair<HeapKey, unsigned int>& b) const {
        if(a.first.second > b.first.second) return true;
        if(a.first.second < b.first.second) return false;
        if(a.first.first > b.first.first) return true;
        if(a.first.first < b.first.first) return false;
        if(a.second > b.second) return true;
        return false;
    }
    
    To switch ascending/descending order, just flip the </> operators. When you use std::tie, you can use members from both a and b to create each tuple. In the example below I've put b.second in the first and a.second in the last. The floats will then be ordered in ascending order while the unsigned int will be in descending order.
            return std::tie(a.first.second, a.first.first, b.second) >
                   std::tie(b.first.second, b.first.first, a.second);
    
  • You access the wrong element when you print out the result. The value to be popped from the heap is in U.back() after you've called std::pop_heap, not in U.front(). You can simply reorganize your code to do it properly:
    while(not U.empty()) {
        std::cout << U.front().second << ' ' << U.front().first << '\n';
        std::pop_heap(U.begin(), U.end(), KeyCompare());
        U.pop_back();        
    }
    

Demo

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • Thank you SO much for this very detailed and well explained answer. I've also tried ChrisMM's answer which seems to also work (by flipping the < to a >, and the <= to a >=), but while I don't exactly understand all the things that goes on, I would guess that using that solution might be flawed as compared to your solution? – OverDemon Dec 06 '22 at 15:38
  • @OverDemon You're welcome! I don't see how using `>=` could work properly. I'll have take a look at what ChrisMM wrote when I'm back at the computer. – Ted Lyngmo Dec 06 '22 at 15:57
  • @OverDemon I don't think the suggestion made by ChrisMM in a comment is working. Can you show what you did in a similar demo that I provided? Other than that, is there anything else left unanswered that I should add to the answer? – Ted Lyngmo Dec 07 '22 at 08:50
  • Hi Ted. Sorry for the slow answer and thanks for your continued help. You're correct that for the example I provided it doesn't seem to actually do anything. I got sort of excited and tried ChrisMM's solution in my own program where it seems to work, but I have to admit that I'm do not have a good overview of everything that goes on by now. I can also so in my own program, when I add an element and it's key I use: push_heap(U.begin(), U.end(), KeyCompare()); make_heap(U.begin(), U.end(), KeyCompare()); And using make_heap in my example instead of push_heap seem to solve that. – OverDemon Dec 07 '22 at 10:33
  • It's my intention to attempt to use your method in my program (and try to understand what happens ^^) as it seems that you're got a good grasp on what goes on, so your solution is logically more solid. – OverDemon Dec 07 '22 at 10:40
  • Also, regarding this: You access the wrong element when you print out the result. The value to be popped from the heap is in `U.back()` after you've called `std::pop_heap`, not in `U.front()` I do use `U.pop_back();` as I can see. – OverDemon Dec 07 '22 at 10:53
  • @Lyngmo Running tests with your code in my program, and it seems to work as I would like (only run a few tests), but I had to alter the code as such: `struct KeyCompare { bool operator()(const std::pair& a, const std::pair& b) const { if(a.first.first > b.first.first) return true; if(a.first.first < b.first.first) return false; if(a.first.second > b.first.second) return true; if(a.first.second < b.first.second) return false; if(a.second > b.second) return true; return false; } };` – OverDemon Dec 07 '22 at 11:16
  • @OverDemon Re: _"`pop_heap`/`pop_back`"_, yes you used both but after `pop_heap` the `front()` element is moved and is the last element in `U` and `pop_back` removes it. To access the element before removing it, use `front()` _before_ `pop_heap` as I showed - or, `pop_heap` and then use `back()` (before `U.pop_back()` - or else it's too late). – Ted Lyngmo Dec 07 '22 at 12:33
  • 1
    Your modified `KeyCompare` seems to extract the elements in a different order to what you described in the question - or I misunderstood the question. :-) For that order you don't even need to define your own comparison function. It already exists in the standard library and is called `std::greater`. Replace your `KeyCompare` definition with this: `using KeyCompare = std::greater>;` and see if that doesn't do exactly what you want. – Ted Lyngmo Dec 07 '22 at 12:36
  • @Lyngmo Thanks for the quick reply. I'm implementing your suggestions and trying to see how it works. In regard to the key ordering, I don't think you misunderstood. It's likely me that overlooked something. I get the order from this part of a paper concerning the algorithm I'm working with [link](https://imgur.com/a/zLIKNUn). While I at first tried to do things as in the picture, it gave a reverse result (biggest first), so I switched the < to a > and had better results. Not a good way of doing things, I know, and I'll try to analyse why, but that's why I do what I did. – OverDemon Dec 07 '22 at 12:56
  • @Lyngmo `using KeyCompare = std::greater>;` seems to work perfectly. I'll continue to test, but I imagine from this point on that if something doesn't work it's probably some other part of my program. Thanks for all the help ^^ – OverDemon Dec 07 '22 at 13:29
  • @OverDemon Great! You're welcome! Have you also considered using `std::priority_queue`? It would look [like this](https://godbolt.org/z/Tr5Mx43Mj) – Ted Lyngmo Dec 08 '22 at 14:08