0

I'm trying to code a graph class in C++, implemented as an adjacency list. I am trying to make this class a template, so that node and edges can contain an arbitrary payload. It is a simple idea, but I am getting confused about how the language might support this.

I want to be able to declare a graph as follows:
graph<int, int> or
graph<string,double> et.c.

First Idea, working backwards from a graph declaration:

template<class ntype, class etype>
class node {

    class edge {
    etype payload;
        node<ntype, etype>* to;
        node<ntype, etype>* from;
    }   

    ntype payload;
    vector< edge<> > incoming; // How to avoid recursion ???
    vector< edge<> > outgoing; // ????
}

template<class ntype, class etype>
class graph {

    vector< node<ntype, etype> > nodes;
}

I am getting terribly confused about the recursion involved, the scope of template arguments etc. I've tried looking at nested classes, typename vs. template, other questions, but it is not making it any clearer. It would seem that C and void pointers are the absolute best way to code this now. Any help or references are greatly appreciated.

  • 1
    `edge` is not a template, drop angle brackets. Add missing semicolons, season to taste, and [it compiles](http://ideone.com/TWDrpa). What seems to be the problem? What recursion you feel the need to avoid? – Igor Tandetnik Aug 30 '14 at 00:18
  • Thank you @IgorTandetnik , you're right. Other questions had me thinking I had to use some sort of nested template declaration. I.e. template< template<>> or whatever. –  Aug 30 '14 at 00:40

1 Answers1

2

I would first start by declaring the edge class outside the node class. I do not see an advantage for making it a nested class in this case. Actually, nested classes bring more cons than pros, in general. This question have good explanations on why.

As for the design of your graph class, I would suggest using one template parameter to represent the payload (any arbitrary data type you may want the graph nodes to carry), and a second one to represent the weight (any numerical value, such as int, float, double, long, etc).

Some graph use-cases might not care whether the graph is weighted or not, so in those cases you could just ignore the weight field, and do not use it (leaving it to some default value, like 0, would be a good practice). I also recommend using an std::list instead of an std::vector to hold the nodes, so that in case you need to add many nodes to your graph, no memory reallocation would happen.

Considering the above, the graph class would look something like the following. Note I use D for the data field (payload), and W for the weight field.

DirectedGraph.h

template <class D, class W> class DirectedGraph{

public: 

    DirectedGraph(){/*...*/}
    ~DirectedGraph(){/*...*/}

    /*Pushes a node into this graph, which will have no connections initially.*/
    void push(GraphNode<D, W> *node){/*...*/}

    /*Erases a node from this graph.*/
    void erase(GraphNode<D, W> *node){/*...*/}

    std::list<GraphNode<D, W>> &getNodes(){/*...*/}

private:

    /*This is the master list holding all nodes of this graph.*/
    std::list<GraphNode<D, W>> nodes;

};

The node and edge (which I called 'Neighbor) classes would then look something like this:

GraphNode.h

/*Forward declaration of the edge structure (see below).*/
template <class D, class W> struct Neighbor;

template <class D, class W> struct GraphNode{

public:

    GraphNode(D data) : data(data) {}
    ~GraphNode(){}

    /*Some data this node element will hold*/
    D data;

    /*Adds an outgoing connection.*/
    void addConnection(Neighbor<D, W> neighbor){ /*...*/}

    /*You may also want to develop a 'removeConnectoin' method here.*/
    /*...*/

    /*Nodes that this one is connected to. Since this actually represents
    a connection (an edge), the struct 'Neighbor' is used, which encapsulates
    a pointer to a 'GraphNode' as well as the weight of the edge.*/
    std::list<Neighbor<D, W>> neighbors;

    /*Nodes that connect to this one. These are the incoming connections. Note
    that this is not of type 'Neighbor' because it does not represente an edge.
    It is just a record of which nodes have an outgoing connection to this one.*/
    std::list<GraphNode<D, W>*> parents;
};


/*This struct represents an edge*/
template <class D, class W> struct Neighbor{
    Neighbor<D, W>(GraphNode<D, W> *node, W weight) : node(node), weight(weight){}
    GraphNode<D, W> *node;
    W weight;
};

One thing to be noticed here is that the only reason for using the Neighbor class is so that we can represent a weight for the edge. If the graph you are going to use will always be unweighted, you could just replace the std::list of Neighbor<D, W> for an std::list of GraphNode<D, W>*. You would also be able to remove the template parameter W from your implementation.

Oh, and of course the graph header file has to include the node one.

I hope this serves as a good start point. Any questions, please let me know.

Community
  • 1
  • 1