##Interface
Your interface locks you into certain design decisions.
std::vector<int> GetAllVertexIDs() const;
Why return a verctor of all the node ID(s)? A more classic approach would be to return an iterator that allows you to iterate over all the nodes.
I am not sure what a depth or a breadth first search of a graph mean?
std::pair<std::vector<int>, std::vector<int>> BreadthFirstSearch(int start_id) const;
std::vector<int> DepthFirstSearch(int start_id, bool recursive = false) const;
Also why are they returning different things? I would expect any type of search to return the same thing. Otherwise how can I write generic code that searches for stuff and be expected to swap one search function for another?
Pass vertex data by reference (or R-Value reference) to prevent excessive copying.
int AddVertex(T value);
// I would have written three versions of this.
int addVertex(T const& value); // Copy value into Graph
int addVertex(T&& value); // Move value into Graph
template<typename... Args>
int emplaceVertex(Args... args); // Construct in place in Graph
Accessing the data of a vertex via a reference. You do fine, but most containers allow you to mutate the date via the reference. So usually you see two versions of this method:
const T & GetVertexData(int vertex_id) const;
// Unless the graph data should be immutable I would have added:
T const& getVertexData(int vertex_id) const;
T& getVertexData(int vertex_id);
A bit more white space in the class declaration to make it more readable would have been nice.
template <typename T>
class Graph
{
public:
using InsertPair = std::pair<int, int>;
Graph(bool directed = false) : directed(directed) {}
int VertexCount() const;
int AddVertex(T const& value);
T const& GetVertexData(int vertex_id) const;
void AddEdge(int start_id, int end_id, int cost = 0);
InsertPair AddEdgeAndVertices(T const& start_value, T const& end_value, int cost = 0);
std::vector<int> GetAllVertexIDs() const;
// BFS returns vector pair <vertex_id, parent of this vertex>
std::pair<std::vector<int>, std::vector<int>> BreadthFirstSearch(int start_id) const;
std::vector<int> DepthFirstSearch(int start_id, bool recursive = false) const;
template <typename U>
friend std::ostream & operator<<(std::ostream & out, Graph<U> & g);
##Naming conventions
There is no standard.
But a common convention is:
- User defined types have an initial upper case letter.
- Objects (variables/methods/functions) have an initial lower case letter.
##File Names
You have split the declaration and definition of your template class into two files (which is fine). But the definition is in a *.cpp file.
A more common convention is to use *.tpp file for the definition. When your code gets installed on other people systems you will then not be installing *.cpp files (which would look strange).
##Code Review
As mention above pass objects by const reference when possible to avoid copying.
template <typename T>
int Graph<T>::AddVertex(T value) // Copy to here that is not needed.
{
int id = VertexCount(); // id is the index into vertices array
vertices.push_back(Vertex(id, value));
return id;
}
Avoid comments that should be obvious from reading the code.
int id = VertexCount(); // id is the index into vertices array
There is already a function that does this:
return std::pair<int, int> (start_id, end_id);
// Easier to write:
return std::make_pair(start_id, end_id);
Avoid this kind of assert.
template <typename T>
void Graph<T>::AddEdge(int start_id, int end_id, int cost)
{
assert(start_id >= 0 && start_id < VertexCount());
assert(end_id >= 0 && end_id < VertexCount());
When you compile this code for production then these assert's disappear (ie they become noops). So they provide no real protection.
The assert should be used to validate that internal state of your object is consistent. Then your test code should try and throw all sorts of data through the public API to make sure the object can not be put in an inconsistent state.
Here you are validating user input. This is not the kind of data that should be evaluated by assert. You can not test all code that uses your code in a testing environment so these assert's provide no real protection. So either do real tests that throw or do no tests and define the contract and assume the user stays within the contract.
Example:
std::vector::at(std::size_t index); // Throws if index is out of range.
std::vector::operator[](std::size_t index); // Does nothing.
// It assumes user has already
// validated the input
// and obeys the contract.
This is doing a lot of work. That may just be discarded by the caller after looking at a few nodes. Also it may become invalid very quickly if the graph is mutated and need to be regenerated.
template <typename T>
std::vector<int> Graph<T>::GetAllVertexIDs() const
{
std::vector<int> vertex_ids(VertexCount());
for (size_t i = 0; i < vertex_ids.size(); ++i)
vertex_ids[i] = i;
return vertex_ids;
}
I would return an iterator (they are cheap to construct). If the graph is mutated then creating another is not an issue. Also you make it return the id or a reference to the object (whichever suits your use case best).
Visitor pattern
I would look into the visitor pattern for doing graph traversal.