Project 4: Graph Algorithms

Graph Exploration

Educational Objectives: After completing this assignment, the student should be able to accomplish the following:

Operational Objectives: Design, implement, and test class ALGraph and Breadth- & Depth- First Surveys.

Deliverables: Files:

bfsurvey.h     # class BFSurvey
dfsurvey.h     # class DFSurvey
myDAG.500      # graph file containing your 500-vertex DAG
readme.txt     # developer/user guide for various classes and tests

Procedural Requirements

  1. The official development | testing | assessment environment is g++47 -std=c++11 -Wall -Wextra on the linprog machines. Code should compile without error or warning.

  2. File readme.txt explains how the software was developed, how it was tested, and how it is expected to be operated.

  3. Copy all files from LIB/proj4/ into your project directory, including the following:

    fgraph.cpp        # general graph test client
    ftopsort.cpp      # topological sort client
    fbfsurvey_ug.cpp  # bfsurvey test client, undirected case
    fbfsurvey_dg.cpp  # bfsurvey test client, directed case
    fdfsurvey_ug.cpp  # dfsurvey test client, undirected case
    fdfsurvey_dg.cpp  # dfsurvey test client, directed case
    makefile          # builds project
    proj4submit.sh    # submit script
    

    Copy these files from LIB/tcpp into your project directory:

    graph.h           # classes ALUGraph and ALDGraph
    topsort.h         # topological sort algorithm
    

    Note these code files are not required to be in your project directory for project build, they are for your reference and study.

  4. Change permissions on the submit script to executable and submit the project by executing the script.

    Warning: Submit scripts do not work on the program and linprog servers. Use shell.cs.fsu.edu to submit projects. If you do not receive the second confirmation with the contents of your project, there has been a malfunction.

Background - ALGraph

  1. Class ALGraph implements the adjacency list representation of a graph whose vertices are assumed to be unsigned integers 0,1,...,n-1. The interface conforms to:

    namespace fsu
    {
      template < typename N >
      class ALUGraph
      {
      public:
        typedef N                                 Vertex;
        typedef fsu::List<Vertex>::ConstIterator  AdjIterator;
    
        void   SetVrtxSize  (N n);
        void   AddEdge      (Vertex from, Vertex to);
        size_t VrtxSize     () const;
        size_t EdgeSize     () const;
        size_t OutDegree    (Vertex x) const;
        size_t InDegree     (Vertex x) const;
        AdjIterator Begin   (Vertex x) const;
        AdjIterator End     (Vertex x) const;
    
        ALUGraph ( );
        ALUGraph ( N n );
      ...
      };
    } // namespace fsu
    

    An AdjIterator is an iterator for the adjacency list of a vertex. The directed graph API is exactly the same (but for the name of the class):

    namespace fsu
    {
      template < typename N >
      class ALDGraph
      {
      public:
        typedef N                                 Vertex;
        typedef fsu::List<Vertex>::ConstIterator  AdjIterator;
    
        void   SetVrtxSize  (N n);
        void   AddEdge      (Vertex from, Vertex to);
        size_t VrtxSize     () const;
        size_t EdgeSize     () const;
        size_t OutDegree    (Vertex x) const;
        size_t InDegree     (Vertex x) const;
        AdjIterator Begin   (Vertex x) const;
        AdjIterator End     (Vertex x) const;
    
        ALUGraph ( );
        ALUGraph ( N n );
      ...
      };
    } // namespace fsu
    

    Much of the implementation code for the undirected and directed cases is identical, so it is profitable to derive one of these from the other. In the derived class, only AddEdge, EdgeSize, and InDegree require re-definition.

  2. Begin(x) returns an AdjIterator which is a forward ConstIterator that iterates through the adjacency list of the vertex v. End(x) returns the end iterator of the adjacency list. So, the loop

    for (typename GraphType::AdjIterator i = g.Begin(x); i != g.End(x); ++i)
    {/*   do something at the vertex *i   */}
    

    encounters all of the vertices adjacent from v in the (directed or undirected) graph g.

  3. The template argument is some unsigned integer type. We are using templates mainly as a convenience so that member functions will not be compiled (or even require implementation) if they are not called by client code.

Code Requirements and Specifications - Algorithms

  1. Algorithms should operate on ALGraph objects via the interface defined above, so that another version of ALGraph can be substituted without modification.

  2. Algorithms should be class templates (in line with the graph class template). See discussion of algorithm classes in the Graphs 1 Lecture Notes.

  3. Algorithms should be tested on both undirected and directed cases. Note that a number of examples are given, and the graph file format can be interpreted as both undirected and directed graph.

  4. The algorithms should also operate correctly using the supplied client programs fbfsurvey.cpp and fdfsurvey.cpp, as built using the supplied makefile. For uniformity and clarity of expectations, here are the APIs expected by the test harnesses:

    namespace fsu
    {
    
      template < class G >
      class BFSurvey
      {
      public:
        typedef G                           Graph;
        typedef typename Graph::Vertex      Vertex;
        typedef typename Graph::AdjIterator AdjIterator;
    
        BFSurvey    ( const Graph& g );
        void Search ( );
        void Search ( Vertex v );
        void Reset  ( );
    
        const fsu::Vector<size_t>&   Distance () const { return distance_; }
        const fsu::Vector<size_t>&   DTime    () const { return dtime_; }
        const fsu::Vector<Vertex>&   Parent   () const { return parent_; }
        const fsu::Vector<char>&     Color    () const { return color_; }
    
        size_t VrtxSize ()           const  { return g_.VrtxSize(); }
        size_t EdgeSize ()           const  { return g_.EdgeSize(); }
    
      private:
        const Graph&                  g_;
        size_t                        time_;      // global sequencing clock
        size_t                        infinity_;
        fsu::Vector < size_t >        distance_;  // distance from search origin
        fsu::Vector < size_t >        dtime_;     // discovery time
        fsu::Vector < Vertex >        parent_;    // for BFS tree
        fsu::Vector < char >          color_;     // various uses
    
        fsu::Vector < bool >          visited_;
        fsu::Vector < AdjIterator >   neighbor_;  // supports NextNeighbor
        fsu::Deque  < Vertex >        conQ_;
        AdjIterator  NextNeighbor(Vertex v);      // iterator to next unvisited nbr of v
    
      public:
        bool traceQue;
        void ShowQueSetup (std::ostream& os) const;
        void ShowQue (std::ostream& os) const;
      };
    
      template < class G >
      void BFSurvey<G>::ShowQueSetup (std::ostream& os) const
      {
        os << "\n  conQueue\n"
           << "  <-------\n";
      }
    
      template < class G >
      void BFSurvey<G>::ShowQue (std::ostream& os) const
      {
        os << "  ";
        if (conQ_.Empty())
          os << "NULL";
        else
          conQ_.Display(os, ' ');
        os << '\n';
      }
    
      template < class G >
      BFSurvey<G>::BFSurvey (const Graph& g)
        : g_(g), time_(0), infinity_(1+g_.VrtxSize()),
          distance_ (g_.VrtxSize(), 1 + g_.EdgeSize()),
          dtime_    (g_.VrtxSize(), 2*g_.VrtxSize()),
          parent_   (g_.VrtxSize(), (Vertex)g_.VrtxSize()),
          color_    (g_.VrtxSize(), 'w'),
          visited_  (g_.VrtxSize(), 0),
          neighbor_ (g_.VrtxSize()),
          conQ_(), traceQue((bool)0)
      {
        for (size_t i = 0; i < g_.VrtxSize(); ++i)
          neighbor_[i] = g_.Begin((Vertex)i);
      }
    
      template < class G >
      void BFSurvey<G>::Reset()
      {
        time_ = 0;
        infinity_ = g_.EdgeSize();
        conQ_.Clear();
        if (visited_.Size() != g_.VrtxSize()) // g_ has changed vertex size
        {
          distance_.SetSize (g_.VrtxSize(), infinity_);
          dtime_.SetSize    (g_.VrtxSize(), 2*g_.VrtxSize());
          parent_.SetSize   (g_.VrtxSize(), (Vertex)g_.VrtxSize());
          color_.SetSize    (g_.VrtxSize(), 'w');
          visited_.SetSize  (g_.VrtxSize(), 0);
          neighbor_.SetSize (g_.VrtxSize());
          for (size_t i = 0; i < g_.VrtxSize(); ++i)
    	neighbor_[i] = g_.Begin((Vertex)i);
        }
        else
        {
          for (size_t i = 0; i < g_.VrtxSize(); ++i)
          {
    	distance_[i]  = infinity_;              // unreachable distance
    	dtime_[i]     = 2*g_.VrtxSize();        // unreachable time
    	parent_[i]    = (Vertex)g_.VrtxSize();  // |V| signifies NULL
    	color_[i]     = 'w';
    	neighbor_[i] = g_.Begin((Vertex)i);
    	visited_[i]  = 0;
          }
        }
      }
    
      // remaining items to be supplied here
    
    } // namespace fsu
    

    Note that implementations of the constructor and the idiosyncratic demonstration display accesories are supplied as well.

    namespace fsu
    {
      template < class G >
      class DFSurvey
      {
      public:
        typedef G                           Graph;
        typedef typename Graph::Vertex      Vertex;
        typedef typename Graph::AdjIterator AdjIterator;
    
        DFSurvey    ( const Graph& g );
        void Search ( );
        void Search ( Vertex v );
        void Reset  ( );
    
        const fsu::Vector<size_t>& DTime()  const { return dtime_; }
        const fsu::Vector<size_t>& FTime()  const { return ftime_; }
        const fsu::Vector<Vertex>& Parent() const { return parent_; }
        const fsu::Vector<char>&   Color()  const { return color_; }
    
        size_t VrtxSize () const            { return g_.VrtxSize(); }
        size_t EdgeSize () const            { return g_.EdgeSize(); }
    
      private:
        const Graph&                  g_;
        size_t                        time_;     // global sequencing clock
        fsu::Vector < size_t >        dtime_;    // discovery time
        fsu::Vector < size_t >        ftime_;    // finishing time
        fsu::Vector < Vertex >        parent_;   // for DFS tree
        fsu::Vector < char >          color_;    // various uses
        fsu::Vector < bool >          visited_;
        fsu::Vector < AdjIterator >   neighbor_; // supports NextNeighbor
        fsu::Deque  < Vertex >        conQ_;
        AdjIterator  NextNeighbor(Vertex v);     // iterator to next unvisited nbr of v
    
      public:
        bool traceQue;
        void ShowQueSetup (std::ostream& os) const;
        void ShowQue (std::ostream& os) const;
      };
    
      template < class G >
      void DFSurvey<G>::ShowQueSetup (std::ostream& os) const
      {
        os << "\n  conStack\n"
           << "  ------->\n";
      }
    
      template < class G >
      void DFSurvey<G>::ShowQue (std::ostream& os) const
      {
        os << "  ";
        if (conQ_.Empty())
          os << "NULL";
        else
          conQ_.Display(os, ' ');
        os << '\n';
      }
    
      template < class G >
      DFSurvey<G>::DFSurvey (const Graph& g)
        : g_(g), time_(0),
          dtime_    (g_.VrtxSize(), 2*g_.VrtxSize()),
          ftime_    (g_.VrtxSize(), 2*g_.VrtxSize()),
          parent_   (g_.VrtxSize(), (Vertex)g_.VrtxSize()),
          color_    (g_.VrtxSize(), 'w'),
          visited_  (g_.VrtxSize(), 0),
          neighbor_ (g_.VrtxSize()),
          conQ_(), traceQue((bool)0)
      {
        for (size_t i = 0; i < g_.VrtxSize(); ++i)
          neighbor_[i] = g_.Begin((Vertex)i);
      }
    
      template < class G >
      void DFSurvey<G>::Reset()
      {
        time_ = 0;
        conQ_.Clear();
        if (visited_.Size() != g_.VrtxSize()) // g_ has changed vertex size
        {
          dtime_.SetSize   (g_.VrtxSize(), 2*g_.VrtxSize());
          ftime_.SetSize   (g_.VrtxSize(), 2*g_.VrtxSize());
          parent_.SetSize  (g_.VrtxSize(), (Vertex)g_.VrtxSize());
          color_.SetSize   (g_.VrtxSize(), 'w');
          visited_.SetSize (g_.VrtxSize(), 0);
          neighbor_.SetSize(g_.VrtxSize());
          for (size_t i = 0; i < g_.VrtxSize(); ++i)
    	neighbor_[i] = g_.Begin((Vertex)i);
        }
        else
        {
          for (size_t i = 0; i < g_.VrtxSize(); ++i)
          {
    	dtime_[i]    = 2*g_.VrtxSize();        // last time stamp is 2|V| -1
    	ftime_[i]    = dtime_[i];
    	parent_[i]   = (Vertex)g_.VrtxSize();  // |V| signifies NULL
    	color_[i]    = 'w';
    	neighbor_[i] = g_.Begin((Vertex)i);
    	visited_[i]  = 0;
          }
        }
      }
    
      // more implementations needed here
    
    } // namespace fsu
    

Requirements - myDAG.500

  1. The file myDAG.500 should contain specs for a graph with 500 vertices. When read as a directed graph in should be acyclic and contain at least 800 edges. When read as an undirected grapg it should contain 3 or more cycles.

  2. All graph and algorithm code should be tested thoroughly. Test code should be developed concurrently with the classes and a test plan should be agreed to and adhered to. The tests, test results, and general development plan should be documented in the text file readme.txt.

  3. Your test programs should work with graphs defined in files using the format illustrated in the distributed data files. The file may begin with optional documentation - lines beginning with # are ignored. After the file documentation, there should be nothing but unsigned integer numbers in decimal notation. The first number is the number of vertices. Then the edges follow, one at a time, consisting of the from vertex followed by the to vertex. (Do not list an edge twice for an undirected graph.) For example:

    #
    # file graph1.10.10
    #
    # This is the graph G1                   0 --- 1     2 --- 6 --- 7
    # depicted to the right                  |           |           |
    #                                        |           |           |
    # G1 has 10 vertices                     3 --- 4 --- 5 --- 8 --- 9 
    # G1 has 10 edges                                Graph G1
    
    10
    0 1 0 3
    2 5 2 6
    3 4
    4 5
    5 8
    6 7
    7 9
    8 9
    

    This file represents the graph G1 from the Graphs 1 lecture notes. Note that each edge is listed only one time, and both ends of each edge are given. Several other graph files are distributed and should be used in your testing, along with any others you may deem appropriate. The format and order of the edge information should not matter to your test code, but it is nice for human readability. For example, the following file specifies the same undirected graph G1:

    10 4 3 5 4 7 9 1 0 2 5 6 2 0 3 6 7 8 9 5 8
    

    10 for the number of vertices, followed by the ten specified undirected edges. (Note this specifies a different directed graph, because some of the edges are listed in opposit direction.) This alternate representation is in file graph2.10.10.

  4. The single command "make" should build all test and demo executables for your project, requiring ONLY the course library, the standard library, and your submitted files. You should test this by copying only your submit files to a separate directory and entering "make".

Hints