Priority queue with Pointers and Comparator C++

别等时光非礼了梦想. 提交于 2019-12-03 07:38:20

Your requirement of implementing bool operator()(Edge*, Edge*) as a regular member of class Edge is possible, but rarely is it done that way.

Comparators for standard library algorithms come in the following idiomatic flavors, all of which must provide a strict weak ordering of the contained objects within the sequence being worked over:

  1. Stand-alone functor class
  2. Stand-alone operator < overload
  3. Object-class member operator < overload.
  4. Stand-alone function
  5. Static class function
  6. Static class functor class
  7. A few others...

The fifth (5) on this list is the closest thing that looks like what they instruction was trying to do, but it is never implemented as operator(). It is possible to do (1) as a member of Edge, but to do so the Edge type must support default construction. I'll explain in a minute how this can be done. Of the options listed above, the best candidates for performance (likeliness of inlining) and implementation ease are (1) and (6) for this specific case. If your queue held actual Edge objects rather than Edge pointers (3) would be a natural fit as well as offer decent performance.

The point of a comparator for ordered containers and container adapters like a priority queue is to compare two items that are held within in compliance with a strict weak ordering. If you don't know what that is, see this link. In implementation, it comes down to this: if, and only if x < y return true, otherwise return false. That means the following must be enforced:

  • x < x always returns false
  • if x < y returns true, then y < x must return false
  • if both x < y and y < x return false, then x is equivalent to y

It is a common mistake to accidentally code one's comparator to not abide by these rules. Guard against that.

Anyway, I digress. One way of doing this correctly given your class definitions and using (1) from the list above is:

Class Edge

class Edge
{
public:
    Edge(Vertex* src, Vertex* dst, double w)
        : source(src), destination(dst), weight(w)
    {
    };

    // note: const-ness
    double getWeight() const { return weight; }

    Vertex const* getSource() const { return source; }
    Vertex* getSource() { return source; }

    Vertex const* getDestination() const { return destination; }
    Vertex* getDestination() { return destination; }

private:
    Vertex* source;
    Vertex* destination;
    double weight;
};

Class CmpEdgePtrs

struct CmpEdgePtrs
{
    bool operator()(const Edge* lhs, const Edge* rhs) const
    {
        return lhs->getWeight() < rhs->getWeight();
    }
};

Class Graph

class Graph
{
    // rest of class stuff

private:
    priority_queue<Edge*, vector<Edge*>, CmpEdgePtrs> edges;
};

Honestly, this is screaming for usage of shared smart pointers, but I leave that to you. The code above stands a very strong chance of inlining the comparator throughout the usage locations within the standard library algorithms that implement the priority queue logic, and as such performance will stand a strong chance of being optimal given your constraints of a container of pointers.


Fulfilling Assigned Requirements

This can be done entirely within class Edge, since it is after all just a class type. The third type passed as the template parameter for the priority queue adapter is something that exposes bool operator(), and there is nothing stopping you from just making an instance of Edge do that for you. It is odd, but it will none-the-less work with a couple modifications:

First, move the bool operator()(const Edge*, const Edge*) const to the Edge class, declared as public. Second, provide a default constructor for Edge, since it will be needed by the internal algorithms of the priority queue when creating the functor to perform the comparisons.

class Edge
{
public:
    // regular parameterized construction
    Edge(Vertex* src, Vertex* dst, double w)
        : source(src), destination(dst), weight(w)
    {
    };

    // ADDED: allows parameterless initialization
    Edge() 
        : source(), designation(), weight() 
    {}

    // note: const-ness
    double getWeight() const { return weight; }

    Vertex const* getSource() const { return source; }
    Vertex* getSource() { return source; }

    Vertex const* getDestination() const { return destination; }
    Vertex* getDestination() { return destination; }

    // ADDED: used when an instance of `Edge` is used as comparator functor
    bool operator ()(const Edge* lhs, const Edge* rhs) const
    {
        return lhs->weight < rhs->weight;
    }

private:
    Vertex* source;
    Vertex* destination;
    double weight;
};


class Graph
{
    // rest of class stuff

private:
    // NOTE: uses Edge-type as the functor type that will
    //  deliver the comparison of Edge pointers.
    priority_queue<Edge*, vector<Edge*>, Edge> edges;
};

This is highly unusual, but has the benefit of allowing the functor direct access to the member variables of Edge objects being compared rather than going through public getters and setters or having to friend a comparator. It has a downside though. There is nothing stopping someone besides the container adapter from constructing Edge objects without specifying source, destination, and weight.

In short, there is little benefit to doing it like this, and potential problems in improper usage of the code required to make this work, and for that, I would recommend the first option instead.

Best of luck

What you're missing is called a forward declaration. In Edge.h, you are allowed to write just class Vertex; on a line by itself. After this line, C++ knows that there is such a class, and that Vertex * therefore is a pointer (and not an the first half of a multiplication).

[edit] This forward declaration replaces #include "Vertex.h" inside Edge.h.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!