I'm trying to practice STL so I implemented breadth first search and depth first search. I have designed the graph class so that vertices can be arbitrary hashable object.
Any feedback would be appreciated
#include <deque>
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <iostream>
template<class Key>
class Graph {
public:
// adds an undirected edge
void addEdge(Key v1, Key v2) { Adj_.insert( {v1,v2} ); }
// prints the adjacency list
void printAdj();
// Graph traversal functions
void BFS(Key v);
void DFS(Key v);
private:
// Adjacency list
std::unordered_multimap<Key,Key> Adj_;
};
template<class Key> void Graph<Key>::printAdj()
{
auto it = Adj_.begin();
while( it != Adj_.end() )
{
std::cout << it->first << ": ";
auto range = Adj_.equal_range(it->first);
for ( auto local_it = range.first; local_it!= range.second; ++local_it )
{
std::cout << local_it->second << " ";
++it;
}
std::cout << "\n";
}
}
//BFS starting from vertex v
//create a queue Q
// mark v as visited and put v into Q
// while Q is non-empty
// remove the head u of Q
// mark and enqueue all (unvisited) neighbours of u
template<class Key> void Graph<Key>::BFS(Key v)
{
// set of visited vertices
std::unordered_set<Key> visited;
std::deque<Key> Q;
Q.push_back(v);
visited.insert(v);
while(Q.size() > 0) // while the queue is non-empty do
{
// store vertex at the front of the queue
v = Q.front();
// remove vertex at the front of the queue
Q.pop_front();
std::cout << v << " ";
// iterate over v's neighbors
for(auto neighbor = Adj_.find(v); neighbor != Adj_.end(); ++neighbor)
{
// check if neighbor has been visited
auto isthere = visited.find(neighbor->second);
if(isthere == visited.end()) // if neighbor has not been visited then
{
// update visited set
visited.insert(neighbor->second);
// add neighbor to back of the queue
Q.push_back(neighbor->second);
}
}
}
std::cout << "\n";
}
//DFS starting from vertex v
//create a stack S
//mark v as visited and push v onto S
//while S is non-empty
// peek at the top v of S
// if v has an (unvisited) neighbor u, mark u and push it onto S
// else pop S
template<class Key> void Graph<Key>::DFS(Key v)
{
// set of visited vertices
std::unordered_set<Key> visited;
std::vector<Key> S;
visited.insert(v);
S.push_back(v);
std::cout << v << " ";
while(S.size() > 0)
{
// peek the top v of the stack
v = S.back();
// find the index of the first unvisited neighbor of v
typename std::unordered_multimap<Key,Key>::iterator neighbor;
for(neighbor = Adj_.find(v); neighbor != Adj_.end(); ++neighbor)
{
// check if neighbor has been visited
auto isthere = visited.find( neighbor->second );
if( isthere == visited.end() ) break;
}
if(neighbor != Adj_.end() ) // the case in which an unvisited neighbor is found
{
// mark the neighbor u as visited
visited.insert(neighbor->second);
// push u onto the stack
S.push_back(neighbor->second);
std::cout << neighbor->second << " ";
}
else
{
S.pop_back();
}
}
}
int main()
{
//example
Graph<std::string> myGraph;
myGraph.addEdge("a","b"); myGraph.addEdge("b","c"); myGraph.addEdge("c","d");
myGraph.addEdge("b","a"); myGraph.addEdge("c","b"); myGraph.addEdge("d","c");
myGraph.addEdge("c","e"); myGraph.addEdge("e","f"); myGraph.addEdge("b","f");
myGraph.addEdge("e","c"); myGraph.addEdge("f","e"); myGraph.addEdge("f","b");
myGraph.addEdge("f","g"); myGraph.addEdge("a","g");
myGraph.addEdge("g","f"); myGraph.addEdge("g","a");
std::cout << "Printing the adjacency list:\n";
myGraph.printAdj();
std::cout << "Performing BFS exploration starting at vertex a:\n";
myGraph.BFS("a");
std::cout << "Performing DFS exploration starting at vertex a:\n";
myGraph.DFS("a");
}
for
loops could be range-based. It will make your code less verbose. \$\endgroup\$for(auto neighbor = Adj_.find(v); neighbor != Adj_.end(); ++neighbor)
? \$\endgroup\$find
, so yeah, there's no escape from using the traditional for loop, unless you try thestd::for_each
function with a lambda, but that might be overkill... \$\endgroup\$for ( auto local_it = range.first; local_it!= range.second; ++local_it )
? \$\endgroup\$