// L-39 MCS 360 Wed 29 Nov 2017 : graph_traversals.cpp

/* A random boolean matrix represents the adjacency matrix of a graph.
   We use this matrix to experiment with graph traversal algorithms. */

#include <iostream>
#include <sstream>
#include <string>
#include <iomanip>
#include <cstdlib>
#include <vector>
#include <list>
#include <queue>

using namespace std;

void allocate_boolean_matrix ( vector< vector<bool> >& mat, size_t dim );
// Allocates space for an adjacency matrix of dimension dim.

void random_directed_graph ( vector< vector<bool> >& mat );
// Fills up the adjacency matrix for a random directed graph.

void write_boolean_matrix ( vector< vector<bool> >& mat );
// Writes the matrix to screen.

void depth_first_search
 ( vector< vector<bool> >& mat, size_t vertex,
   list<size_t>& visited, bool verbose );
/*
   Applies depth-first search to visit all vertices in a graph.
   The visited vertices are collected in the list visited.

   ON ENTRY :
     mat        adjacency matrix of the graph;
     vertex     start vertex;
     visited    should be an empty list;
     verbose    if true, vertices are written to screen.

   ON RETURN :
     visited    list of visited vertices. */

void breadth_first_search
 ( vector< vector<bool> >& mat, size_t vertex, bool verbose );
/*
   Applies depth-first search to visit all vertices in a graph.

   ON ENTRY :
     mat        adjacency matrix of the graph;
     vertex     start vertex;
     verbose    if true, vertices are written to screen. */

string depth_first_search_tree
 ( vector< vector<bool> >& mat, size_t vertex, list<size_t>& visited,
   bool verbose, size_t level );
/*
   Applies depth-first search to visit all vertices in a graph.
   The visited vertices are collected in the list visited.
   Returns the string representation of the depth-first search tree.

   ON ENTRY :
     mat        adjacency matrix of the graph;
     vertex     start vertex;
     visited    should be an empty list;
     verbose    if true, the visited vertices are written to screen;
     level      depth level of the recursion.

   ON RETURN :
     visited    list of visited vertices. */

string breadth_first_search_tree
 ( vector< vector<bool> >& mat, size_t vertex, bool verbose );
/*
   Applies breadth-first search to visit all vertices in a graph.
   Returns the string representation of the breadth-first search tree.

   ON ENTRY :
     mat        adjacency matrix of the graph;
     vertex     start vertex;
     verbose    if true, the visited vertices are written to screen. */

int main ( void )
{
   size_t seed = 1511924643; // time(NULL);
   cout << "The seed : " << seed << endl;
   srand(seed);

   cout << "Give the number of vertices : ";
   size_t n; cin >> n;

   vector< vector<bool> > mat;

   allocate_boolean_matrix(mat, n);
   random_directed_graph(mat);
   cout << "A random Boolean matrix :" << endl;
   write_boolean_matrix(mat);
   cout << "Running a depth-first search ..." << endl;
   list<size_t> vertices;
   depth_first_search(mat,0,vertices,true); cout << endl;

   list<size_t> vertices2;
   string dftree = depth_first_search_tree(mat,0,vertices2,false,0);
   cout << "The depth-first search tree :" << endl;
   cout << dftree;

   cout << "A random Boolean matrix :" << endl;
   write_boolean_matrix(mat);
   cout << "Running a breadth-first search ..." << endl;
   breadth_first_search(mat,0,true); cout << endl;
   string bftree = breadth_first_search_tree(mat,0,false);
   cout << "The breadth-first search tree :" << endl;
   cout << bftree;

   return 0;
}

void allocate_boolean_matrix ( vector< vector<bool> >& mat, size_t dim )
{
   for(size_t i=0; i<dim; i++)
   {
      vector<bool> row;

      for(size_t j=0; j<dim; j++) row.push_back(0);

      mat.push_back(row);
   }
}

void random_directed_graph ( vector< vector<bool> >& mat )
{
   const size_t dim = mat.size();

   for(size_t i=0; i<dim; i++)
      for(size_t j=0; j<dim; j++)     // place edges at random
         mat[i][j] = (rand() % 2); // generate random bits
}

void write_boolean_matrix ( vector< vector<bool> >& mat )
{
   const size_t dim = mat.size();
   
   for(size_t i=0; i<dim; i++)
   {
      for(size_t j=0; j<dim; j++)
         cout << " " << mat[i][j];
      cout << endl;
   }  
}

void depth_first_search
 ( vector< vector<bool> >& mat, size_t vertex,
   list<size_t>& visited, bool verbose )
{
   if(verbose) cout << " -> " << vertex;
   visited.push_back(vertex);
   for(size_t neighbor=0; neighbor<mat.size(); neighbor++)
      if(mat[vertex][neighbor])
      {
         list<size_t>::iterator it;
         it = find(visited.begin(), visited.end(), neighbor);
         if(it == visited.end())
            depth_first_search(mat,neighbor,visited,verbose);
      }
}

string depth_first_search_tree
 ( vector< vector<bool> >& mat, size_t vertex,
   list<size_t>& visited, bool verbose, size_t level )
{
   string tree;
   ostringstream outvtx;

   for(size_t k=0; k<level; k++) outvtx << "   ";
   outvtx << "-> " << vertex << endl;
   tree = outvtx.str();

   if(verbose) cout << tree;
   visited.push_back(vertex);

   for(size_t neighbor=0; neighbor<mat.size(); neighbor++)
      if(mat[vertex][neighbor])
      {
         list<size_t>::iterator it;
         it = find(visited.begin(), visited.end(), neighbor);
         if(it == visited.end())
         {
            string subtree
               = depth_first_search_tree(mat,neighbor,visited,verbose,level+1);
            tree = tree + subtree;
         }
      }
   return tree;
}

void breadth_first_search
 ( vector< vector<bool> >& mat, size_t vertex, bool verbose )
{
   list<size_t> queue;
   queue.push_back(vertex);
   list<size_t> visited;

   while(queue.size() > 0)
   {
      size_t first = queue.front();
      queue.pop_front();
      if(verbose) cout << " -> " << first;
      visited.push_back(first);

      for(size_t neighbor=0; neighbor<mat.size(); neighbor++)
      {
         if(mat[first][neighbor])
         {
            list<size_t>::iterator itv;
            itv = find(visited.begin(), visited.end(), neighbor);
            if(itv == visited.end())
            {
               list<size_t>::iterator itq;
               itq = find(queue.begin(), queue.end(), neighbor);
               if(itq == queue.end()) queue.push_back(neighbor);
            }
        } 
      }
   }
}

string breadth_first_search_tree
 ( vector< vector<bool> >& mat, size_t vertex, bool verbose )
{
   vector< vector<bool> > vismat; // only visited edges
   allocate_boolean_matrix(vismat, mat.size());

   list<size_t> queue;
   queue.push_back(vertex);
   list<size_t> visited;

   while(queue.size() > 0)
   {
      size_t first = queue.front();
      queue.pop_front();
      if(verbose) cout << " -> " << first;
      visited.push_back(first);

      for(size_t neighbor=0; neighbor<mat.size(); neighbor++)
      {
         if(mat[first][neighbor])
         {
            list<size_t>::iterator itv;
            itv = find(visited.begin(), visited.end(), neighbor);
            if(itv == visited.end())
            {
               list<size_t>::iterator itq;
               itq = find(queue.begin(), queue.end(), neighbor);
               if(itq == queue.end())
               {
                  queue.push_back(neighbor);
                  vismat[first][neighbor] = true;
               }
            }
        } 
      }
   }
   list<size_t> dfvis;
   return depth_first_search_tree(vismat, vertex, dfvis, false, 0);
}
