Version 06/23/19
Educational Objectives: After completing this assignment, the student should be able to accomplish the following:
Operational Objectives:
Deliverables:
wgraph.h # weighted graph framework kruskal.h # contains algorithm Kruskal<G> prim.h # contains algorithm Prim<G> dijkstra.h # contains algorithm Dijkstra<G> bellford.h # contains algorithm BellFord<G> log.txt # work log / test diary
Note the restriction on use of std:: libraries (under Requirements).
This is a fairly large development project that is somewhat open-ended. The bulk of the code required is in design and implementation of the weighted graph classes. The four algorithms operate on objects from that framework. Therefore it is extremely important that you test the graph classes thoroughly to eliminate the possibility that errors in the outcomes of the algorithms are due to faulty graph classes.
To get started, you need to develop a weighted graph framework for weighted graphs, along the lines of the unweighted version in LIB/graph/graph.h. You need to represent weights of edges. There are at least three ways to do this:
These each have advantages and dangers.
Option 1: Weight as a mapping weight_:{edges} -> {real numbers}
A strong advantage for following option #1 is that the weighted graph classes can re-use code from the unweighted classes, as follows:
namespace fsu { template < typename N > ALUG class ALUWGraph : public ALUGraph <N> / \ { ALDG ALUWG ... \ }; ALDWG template < typename N > blue = defined in graph.h class ALDWGraph : public ALDGraph <N> red = defined in wgraph.h { ... (Option 1) }; } // namespace fsu
See the weighted graph notes for more detail.
Option 2: Weight as a component of an Edge class
In option 1, edges are represented logically using adjacency lists of vertices, but edges have no concrete representation as objects. In option 2 edges are actual objects consisting of two vertices and a weight. The option 1 adjacency lists of vertices, as in the base class unweighted cases, are replaced with lists of pointers or references into an inventory of actual edge objects. Thus it is not practical to derive weighted graph classes from the unweighted versions as we can in option 1. We can still take advantage of code re-use by deriving the directed case from the undirected:
namespace fsu { template < typename N > ALUG ALUWG class ALUWGraph / / { ALDG ALDWG ... }; template < typename N > blue = defined in graph.h class ALDWGraph : public ALUWGraph <N> red = defined in wgraph.h { ... (Option 2) }; } // namespace fsu
Moreover, the API and all of the code implementing code, while not re-usable, is easily adapted to the new model.
In #2 you will need an "edge inventory" (a collection of Edge objects) and a way to refer into that inventory when defining a graph, so that there is never more than one copy of a given edge object anywhere. (E.g., make an adjacency list of pointers into inventory.) This is easier to manage for directed graphs. For undirected graphs you have to be careful that adjacent vertices refer to the same edge (not 2 copies), and ensure that {x,y} == {y,x} with un-ambiguous weight. Finally, ensure that the edge inventory has no redundancies.
For this project, follow Option 1.
The project cannot move to graph search until the supporting weighted graph framework has been built, tested, and certified correct and bug-free. Start on this phase ASAP with a class and test design.
Use the names ALUWGraph and ALDWGraph for the undirected and directed classes.
One weighted graph utility you will need is a function
template < class G > bool WLoad (std::istream& inStream, G& g);
that instantiates a weighted graph from data in a file (after first skipping file documentation, lines that begin with '#'). This can be modelled on the Load function for unweighted graphs.
WLoad will of course need a file specification to work with:
# # documentation begins with '#' at the first character # and continues as long as lines begin with '#' # # first entry is unsigned int n = number of vertices # followed by triples x y w # x,y represent vertices of edge from x to y (unsigned < n) # w is a real number representing weight of edge # n x y w x y w x y w ... x y w
As in the unweighted case, the same file can represent either a directed or an undirected graph, determined by the type being loaded into.
These algorithms should follow the general model we use for BreadthFirstSurvey: a template class that takes a graph reference in its constructor. Follow the models discussed in detail in the Weighted Graphs Notes.
template < class G> class Kruskal { typedef G Graph typedef typename G::Vertex Vertex; typedef fsu::Edge<Vertex> Edge; typedef fsu::Vector<Edge> Container; typedef fsu::GreaterThan<Edge> Predicate; typedef fsu::PriorityQueue<Edge,Container,Predicate> PQ; public: // constructor initializes all class variables in init list Kruskal (const G& g); // implementing the algorithm void Init(bool verbose = 0); // preps class variables for execution of algorithm void Exec(bool verbose = 0); // runs algorithm // extracting information const fsu::List<Edge>& MST() const; double Weight() const; ... // expand API optional private: const Graph& g_; ... PQ pq_; }; template < class G> class Prim; // same API as Kruskal
The goal is for the following kind of declaration to work:
typedef size_t Vertex; // template argument for N typedef fsu::ALUWGraph <Vertex> WGraphType; // ALUWGraph WGraphType g; fsu::Prim <WGraphType> prim(g);
The above should create an undirected adjacency list weighted graph g and a Prim<G> algorithm object prim that operates on g.
The method Exec() should play the equivalent role in all of the algorithms. For example, after the declarations above,
prim.Init(); prim.Exec();
should be the calls that invoke the Prim process.
Note: You may implement the "lazy" versions of Prim and Dijkstra (i.e., using a min-heap priority queue without the extra functionality of "decrease_key").
template < class G> class Dijkstra { typedef G Graph typedef typename G::Vertex Vertex; ... // more definitions optional public: // constructor initializes all class variables in init list Dijkstra (const G& g); // implementing the algorithm void Init(Vertex source); // preps variables for startup of algorithm void Exec(); // executes algorithm // extracting information const fsu::Vector<double>& Distance() const; const fsu::Vector<size_t>& Parent() const; void Path(Vertex x, fsu::List<Vertex>& path) const; ... // expand API optional private: // data const Graph& g_; ... private: // methods void Relax(Vertex v) ... }; template < class G> class BellFord; // same API as Dijkstra
The official development/testing/assessment environment is specified in the Course Organizer. Code should compile without warnings or errors.
In order not to confuse the submit system, create and work within a separate subdirectory cop4531/proj4.
Maintain your work log in the text file log.txt as documentation of effort, testing results, and development history. This file may also be used to report on any relevant issues encountered during project development.
General requirement on genericity. Use generics whenever possible. For example:
In short: re-use components from LIB (or your own versions) whenever possible. Don't re-invent the wheels.
Begin by copying all files in LIB/proj4 into your project directory. You should see at least these:
deliverables.sh # submission configuration file fwgraph.cpp # general test harness for option 1 weighted graph classes ranwgraph.cpp # random weighted graph generator ranwgraph_ER.cpp # random weighted Erdos-Renyi graph generator edge.api # copy of LIB/graph/edge.h - defines Edge class template mst.cpp # mst client program sssp.cpp # sssp client program makefile # builds essentials
Follow these steps when you are ready to submit:
Warning: Submit scripts do not work on the program and linprog servers. Use shell.cs.fsu.edu or quake.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.
These libraries may NOT be used:
<string> <set> <unordered_set> <map> <unordered_map> <algorithm>
There are equivalent components of the fsu::library in ~cop4531p/LIB that may be used.
Verbose Behavior. For both Kruskal and Prim, the following should be the last few lines of code in Init and Exec:
Init(bool verbose) { ... if (verbose) { std::cout << "Init: Initial edges in PQ: " << pq_.Size() << '\n'; if (pq_.Size() < 20) { std::cout << "PQ.Dump():\n"; pq_.Dump(std::cout, '\n'); std::cout << '\n'; } } } Exec(bool verbose) { ... if (verbose) { std::cout << "Exec: Remaining edges in PQ: " << pq_.Size() << '\n'; if (pq_.Size() < 20) { std::cout << "PQ.PopOut():"; pq_.PopOut(std::cout, '\n'); std::cout << '\n'; } } }
Here pq_ is the private class variable storing the priority queue used in algorithm control.
Kruskal, Prim, Dijkstra, and BellFord should faithfully implement the algorithm of the same name, as discusses in the Graph notes and textbook. Specifically: output should match exactly that of the benchmark programs in LIB/area51.