This is a good start on making a generic graph class not tied to a specific type. Your naming is reasonable. Here are some things I would do differently.
Improving Depth-First Search
If you want to keep the structure of your depth-first search the same but remove the need to have a caller to create and/or clear the visited
list, you can make a private function that's called by the public one. Something like this:
template<typename T>void Graph<T>::dfs(T start, T end){ std::set<T> visited; int flag = 0; dfs_impl(start, end, visited, flag);}
Then you would take your current Graph<T>::dfs
would be renamed dfs_impl()
and pass in the visited
and flag
from the new dfs
function.
Separate Display Logic and Business Logic
You are printing various things within your class methods. Generally, this is a bad idea. There's a principle called Separation of Concerns. The idea is that a method like dfs()
will perform the task of finding the path to the end
node and return whether it succeeded or not to the caller. The caller will then either print the result or call another function to print it.
The reason you want to do something like this is because it's likely in the future that you will use this method in many different ways. For example, you might determine if there's a path from one node to another and then perform some action, like sending the result over the network to another machine, or displaying an alert to the user, or anything else. If the depth-first search prints out its result, that would be odd if you're using it to determine another action to take. Likewise with the bread-first search.
I would also have the functions return either whether they succeeded or return the actual path through the graph from start
to end
. That would allow the caller to determine what they want to do with the information.
Breadth-first Search
I think your breadth-first search looks great! Other than moving the print statements out of it, I don't see a lot that needs to change. I'd probably write it in a similar way.
One thing I've seen done to improve performance is to have a flag in each element of the graph that says whether they've already been visited. If you did that, you could avoid searching the visited
list on every iteration of the for
loop. You would have to start the function by clearing the flags in every node. That single pass over all the nodes would be more efficient in cases where the path between start
and end
is very long. Of course, when the paths you're searching tend to be very short, it would be less efficient. So there's a trade-off.
Other Things
I would test the performance of using a std::list
vs. a std::vector
for the adjacency list. You have the option of pre-allocating a number of spaces in the std::vector
which might make insertions faster, and iterating over it might also be faster, depending on the implementation. (As always, profile to be sure.)