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
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.
File readme.txt explains how the software was developed, how it was tested, and how it is expected to be operated.
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.
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.
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.
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.
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.
Algorithms should operate on ALGraph objects via the interface defined above, so that another version of ALGraph can be substituted without modification.
Algorithms should be class templates (in line with the graph class template). See discussion of algorithm classes in the Graphs 1 Lecture Notes.
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.
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
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.
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.
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.
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".
Even though our typical use of these classes will have the template argument N = size_t, it will be very useful in your implementation code to distinguish between type Vertex and type size_t and carefully cast between the two when the two types have different connotations. For example, if Vertex x and size_t i, then Begin((Vertex)i) and parent[(size_t)x].
Several graph files are distributed in LIB/proj4. Some of these are named graph.v.e and some are named are named dag.v.e, where v is the number of vertices and e the number of edges of the graph represented by the file. DO NOT rely on these suffixes in your programs, they are for human convenience only (and in some instances may not even be accurate). Those named dag are purported to be acyclic when interpreted as directed graphs, but will have cycles when interpreted as undirected graphs.
A thorough understandiing of the material on the Graphs 1 Lecture Notes will be helpful.
The following is output from a test of DFSSurvey run on G1 (undirected case):
linprog2> fdfsug.x graph1.10.10 Begin DFSurvey functionality test graph type: undirected adjacency list Load complete Input file: graph1.10.10 VrtxSize = 10 EdgeSize = 10 df survey data ============== vertex dtime ftime parent color ------ ----- ----- ------ ----- 0 0 19 NULL b 1 1 2 0 b 2 6 15 5 b 3 3 18 0 b 4 4 17 3 b 5 5 16 4 b 6 7 14 2 b 7 8 13 6 b 8 10 11 9 b 9 9 12 7 b Vertex discovery order: 0 1 3 4 5 2 6 7 9 8 Vertex finishing order: 1 8 9 7 6 2 5 4 3 0 End DFSurvey functionality test linprog2>
Note the table of survey data and the output of the vertices in preorder and postorder.
The following is output from a test of BFSSurvey run on G1 (directed case):
linprog2> fbfsdg.x graph1.10.10 Begin BFSurvey functionality test graph type: directed adjacency list Load complete Input file: graph1.10.10 VrtxSize = 10 EdgeSize = 10 df survey data ============== vertex distance dtime parent color ------ -------- ----- ------ ----- 0 0 0 NULL b 1 1 1 0 b 2 0 7 NULL b 3 1 2 0 b 4 2 3 3 b 5 3 4 4 b 6 1 8 2 b 7 2 9 6 b 8 4 5 5 b 9 5 6 8 b Vertex discovery order: 0 1 3 4 5 8 9 2 6 7 grouped by distance: [ ( 0 ) ( 1 3 ) ( 4 ) ( 5 ) ( 8 ) ( 9 ) ] [ ( 2 ) ( 6 ) ( 7 ) ] End BFSurvey functionality test linprog2>
Again the table shows the survey data. The vertex discovery order, grouped by distance from the search vertex, is also shown. (BFS discovers and finishes vertices in the same order.) The discovery order grouped by distance uses [ ] to delimit trees in the forest and ( ) to delimit vertices the same distance away from the root of the tree.
The discovery and finishing order are computed post-survey from the timestamps. (Discovery time was added to the usual BFS to facilitate this.) The discovery order "grouped by distance" output in fbfsurvey uses both distance and time. One could also output a Lisp-syntax record of the search forest for either survey.
Sample executables are available in LIB/area51. These show some elaborations such as digraphs, topological sort for digraphs, and output from the surveys that isn't direct. I added discovery time to BFSurvey, which is handy information to have, as illustrated by the post-survey computation of discovery order. Decode the names as follows:
fgraph.x # general test of Graph classes - can supply detailed log fdfs_ud.x # functionality test of DFSurvey - undirected graphs fdfs_dg.x # functionality test of DFSurvey - directed graphs fbfs_ud.x # functionality test of BFSurvey - undirected graphs fbfs_dg.x # functionality test of BFSurvey - directed graphs ftopsort.x # functionality test of TopSort - directed graphs only