Version 11/30/18
Educational Objectives: After completing this assignment, the student should be able to accomplish the following:
Operational Objectives: Develop a graph framework to accommodate weighted edges in both undirected and directed graphs. Use the framework to develop an implementation of Algorithm A* for planar graphs, with heuristic defined as Euclidean distance in the plane. Illustrate the framework and algorithm A* with a "smart" route planning system.
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 BellmanFord <G> ... # client and helper programs TBA log.txt # work log / test diary
Note the restriction on use of std:: libraries (under Requirements).
This is a large development project that is somewhat open-ended to give you the freedom of design decisions (constrained as usual by client program expectations). Due to the size of the code base you need to create, this will be take time. There are 5 weeks between the release date and due date, and you will need to take advantage of all of that lead time. Think, develop, and test as you go.
Waiting until late in the window to start will almost certainly lead to a disastrous confusion of software mis-steps, bugs, and a general un-rescuable mess. Don't be that person drowning in a whirlpool of bad code and misunderstandings.
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.
In #1 you need to be careful not to use too much memory. A matrix is out of the question for sparse graphs, so you would need some kind of associative array that can restrict the domain to actual edges of the graph (and not bloated to all possible edges, as a matrix would do). One advantage of this design direction is that most of the (unweighted) graph framework itself can be re-used. One pitfall is that in the undirected case an edge {x,y} is the same edge as {y,x}, so there should be only one entry for this edge in the mapping. This nuance can be accomplished by using two different definitions of a private member function
static fsu::String Key ( Vertex x , Vertex y );
that produces an unambigous key for an edge [x,y] whose weight is stored in the weight map. (Hint: see the code illustrating the memoized knapsack problem in the Dynamic Programming Notes. ToHex is probably faster than ToDec.)
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.
In #3 you have to be careful that the entries <y,w> in adj_[x] and <x,w> in adj_[y] agree on what the weight is for an edge [x,y] and, in the undirected case, {x,y} == {y,x}.
Whichever way you choose, use the same choice for both undirected and directed weighted graphs.
The design decision will have dramatic effect on implementations of the various weighted graph algorithms. You can be assured that either path can be made to work efficiently. The main thing you want to avoid is having to debug your weighted graph framework and your search algorithms at the same time.
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. If feasible, it is highly beneficial to use object-based design to derive these classes from existing base classes. For example:
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 { ... (plan A) }; } // namespace fsu
Deriving from the unweighted graph classes defined in LIB/graph/graph.h, as illustrated above, will save a fair amount of re-work, because the weighted graph objects "are" ordinary unweighted graphs and therefore functions and algorithms defined for the parent type can be applied directly to the child type. If such design is not possible, you are encouraged to seek another object-based design that accomplishes a comparable amount of code re-use. (Recall from the course syllabus that code re-use is a highly desireable property in design.)
If the class hierarchy pictured above (plan A) is not feasible, there are other ways that yield some code re-use, such as:
ALUG ALUG ALUWG / \ / / ALDG ALUWG ALDG ALDWG / ALDWG (plan C) (plan B)
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 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.
template < class G>
class Kruskal
{
typedef G Graph
typedef typename G::Vertex Vertex;
typedef fsu::Edge<Vertex> Edge;
... // more definitions optional
public:
// constructor initializes all class variables in init list
Kruskal (const G& g);
// implementing the algorithm
void Init(); // preps class variables for execution of algorithm
void Exec(); // runs algorithm
// extracting information
const fsu::List<Edge>& MST() const;
double Weight() const;
... // expand API optional
private:
const Graph& g_;
...
};
template < class G>
class Prim; // same API as Kruskal
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 BellmanFord; // same as Dijkstra
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. This is following the precedent set in fbfsurvey.cpp for unweighted graphs.
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.
We will be releasing simple tester client code for each of the algorithms.
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").
This last phase is to apply knowldege and experience gained to the more specific problem of finding shotest paths in planar graphs, using the EA* algorithm - which is the A* algorithm that uses euclidean "as the crow flies" distance as its heuristic. We will iron out details for phase 3 working with early finishers of phase 2. We already have the Point class and euclidean distance defined in euclidean.h.
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 euclidean.h # defines class Point and the euclidean distance metric ...
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.
Check out: