CMSC 109 Programming Topics in C++ Project

 
  The goal of the semester project is be sure you can 
  create a C++ program that follows a specification,
  implements an algorithm, reuses library and other classes
  and has a low defect density.

Specification

Write a program in Standard C++ that reads a weighted directed graph,
computes and prints the shortest weighted distance from the first
vertex to all other vertices.

The Standard C++ program must stay within the 'constraints' given below
as comments in the file "dijkstra.cpp"

The project must be submitted on gl.umbc.edu using

    submit cs109 project dijkstra.cpp  (and any other files you need)

Use the file extension   .cpp  for grading using Visual C++ 6.0
Use the file extension   .C    for grading on gl using CC -n32 -Iinclude
Use the file extension   .cc   for grading on gl using g++
                               (errors about weak bindings will be ignored) 

Sample "structure" of main program:

// dijkstra.cpp   CMSC 109 Spring 1999 project definition
//
// Given a connected directed graph as input (a set of weighted edges)
// Read in the graph and compute the shortest weighted distance
// from the source (first vertex in the input data) to every other vertex.
// Print the list of vertices in the order read, one per line,
// followed by one space and the distance from the source.
//
// Constraints:
//   Use good Standard C++ style
//   Use C++ STL as much as possible.
//   Do use any library header files of the form <*.h>
//   You may define your own header files and include them "*.h"
//   Justify, by writing a paragraph, if you use any include files
//             that are C library header files for Standard C++
//
// Hints:   Suggested but not required. You can do it your way.
//          You may use graph_read.cpp and edit it to suit your needs.
//          You may want to put your data structure definitions in
//          a header file, "graph.h" included below, would be such a file.
//

#include <iostream>
using namespace std;

#include "graph.h"                     // or have everything in one file

int main(int argc, char *argv[])       // standard main program definition
{

  graph_type G;              // The graph is stored in the object G
  graph_type::iterator vert; // need an iterator through G
  Adj temp;                  // need a temporary Adj list
  Adj::iterator p;           // need a temporary iterator through temp
  
  cout << "dijkstra running" << endl; // trace execution 
  
  if(argc<2) // be sure user supplied a file name
  {
    cout << "no file on command line. Exiting." << endl;
	return 0;
  }

  graph_input(argv[1], G); // input the graph data from file
                           // make a function or use graph_read.cpp

  // initialize each vertex to not done
  for(vert=G.begin(); vert!=G.end(); vert++) vert->set_done(false);
	  
  // pseudo code for Dijkstra's shortest path algorithm (must make this C++)
  
  // while some vertex not done
  //   do u <- vertex with smallest distance that is not done
  //     for each edge v in u's adjacency list
  //       do  if  v's distance is greater than u's distance + v's weight
  //              then v's distance becomes u's distance + v's weight
  //   mark u done

  // print shortest path result
  
  for(vert=G.begin(); vert!=G.end(); vert++)
      cout << vert->get_vertex() << " " << vert->get_distance() << endl;
  cout << endl;  
  
  cout << "dijkstra finished" << endl;
  return 0;
}

Sample Input and file reading code

The input is a graph. The graph is defined by weighted edges.
Each line has a vertex1, a vertex2 and an integer weight.

Sample input:  dijkstra1.dat 

Sample code to read input into a vector of lists data structure:
  graph_read.cpp 

Then the output of graph_read.cpp shows the data structure that is built:
  graph_read.out 

The above was compiled and executed using
     g++ -o graph_read  graph_read.cpp
     graph_read  dijkstra1.dat  >  graph_read.out

For g++ on gl machines (or IRIX 6.5.3 there is a problem with fstream)
Thus use graph_read1.cc source code
and compile    g++ -o graph_read1  graph_read1.cc
and execute    graph_read1 < dijkstra1.dat > graph_read1.out

Sample Output

   See "Project Testing" below.

Project Implementation

  Implement Dijkstra's Shortest Path Algorithm using an
  adjacency list data structure. (The sample code "graph_read.cpp"
  referenced above builds one possible adjacency list data structure.)

  A graph presented as a diagram looks like:



  where a circle represents a vertex and a line represents an edge.
  The number on the edge line is called a weight.

  The computer input data is easily represented by three items on
  a line for each edge: vertex1  vertex2  weight  given with data
  types  string  string  int  respectively in C++.

  S  U  10
  S  X  5
  U  V  1
  U  X  2
  V  Y  4
  X  U  3
  X  V  9
  X  Y  2
  Y  S  7
  Y  V  6

  A graph can be stored in a computer as a data structure called
  an adjacency list. The initial data structure for the data above is:



  Algorithm: Dijkstra's Shortest Path applied to the data above:

  find a pointer u to a not done, F, vertex in G that is the smallest
    (u points to  [S 0 F] for this graph)
    for each edge in u's adjacency list, get a pointer v to G
      (our first v points to [U 10000 F], v actually contains "&U")
      if v's distance is greater than u's distance + v's weight
         then set v's distance to u's distance + v's weight.
         (our case  if 10000 > 0 + 10  then  10000 replaced by 0 + 10)
    set u done. (our case [S 0 F] becomes [S 0 T] )

  G after 1st pass  G after 2nd pass  G after 3rd pass  G after 4th pass
  +--------------+  +--------------+  +--------------+  +--------------+
  | S  0       T |  | S  0       T |  | S  0       T |  | S  0       T |
  | U  10      F |  | U  8       F |  | U  8       F |  | U  8       T |
  | X  5       F |  | X  5       T |  | X  5       T |  | X  5       T |
  | V  100000  F |  | V  14      F |  | V  13      F |  | V  9       F |
  | Y  100000  F |  | Y  7       F |  | Y  7       T |  | Y  7       T |
  +--------------+  +--------------+  +--------------+  +--------------+

Project Testing

  A starter set of test files and correct results is provided.
  Add tests to these to be sure the specification is met.

  dijkstra1.dat input file results in
  dijkstra1.out as an output file.
  From running dijkstra dijkstra1.dat > dijkstra1.out

  dijkstra2.dat input file results in
  dijkstra2.out as an output file.
  From running dijkstra dijkstra2.dat > dijkstra2.out

  dijkstra3.dat input file results in
  dijkstra3.out as an output file.
  From running dijkstra dijkstra3.dat > dijkstra3.out

  You should print the input file name and the input data,
  the debug print showing the data structure may be deleted.

  The results MUST be printed.

Amount of effort

  DO NOT LEAVE THIS TO THE LAST DAY!
  Expect to spend 16 hours of studing the problem and
  very careful programming on this project.
  (It will take 40 hours if you are sloppy or careless.)
  Comment your code as much for yourself as for grading.

  Use indentation to match the structure of your code.
  You can leave debug print in your code.
  It is more professional to have debug print under the control
  of something to turn it off.

Grading policy


 Points can be lost by: 
   Partially incorrect results,
   Lack of comments to follow what code is doing at a coarse level.
   Really bad, Standard C++ programming practice.


 Partial credit will be given for partially working code.
 Code must compile, link and get some amount of output for partial credit.

NOTE: not working with g++ on gl machines Spring 1999, use graph_read1.cc !

// graph_read.cpp   get file name, open file, read file

#include <iostream>                    // console I/O
#include <fstream>                     // file I/O
#include <string>                      // for input data
#include <algorithm>
#include <list>
#include <vector>
using namespace std;                   // bring standard names into scope

class my_vertex;  // just declaring my_vertex as a type

class my_edge     // the type of objects in the adjacency lists
{
  public:
    my_edge(string Aedge, int Aweight){edge=Aedge; weight=Aweight;}
    inline string get_edge(){ return edge;}
    inline my_vertex * get_vertex_ptr(){return vertex;}
    inline void set_vertex_ptr(my_vertex * ptr){vertex=ptr;}
    inline int get_weight(){ return weight;}
  private:         // these data items are for the adjacency list
    string edge;   // name of vertex (edge is from parent vertex to here)
    my_vertex * vertex; // pointer to this vertex in the vector
    int weight;    // weight of this edge
};
typedef list<my_edge> Adj;  // the type of each adjacency list
  
class my_vertex   // the type of objects in the vector of vertices
{
  public:
    my_vertex(string Avertex, int d)
              {vertex=Avertex; distance=d;}
    my_vertex(string Avertex, int d, my_edge Aedge)
              {vertex=Avertex; distance=d; edge_list.push_back(Aedge);}
    inline int get_distance(){return distance;}
    inline string get_vertex(){return vertex;}
    inline Adj get_edges(){return edge_list;}
    inline Adj * get_Adj_ptr(){return &edge_list;}
    inline void add_edge(string vert2, int weight)
                         {edge_list.push_back(my_edge(vert2, weight));}
    inline void set_distance(int d){distance=d;}
    inline bool exist(string s){return vertex==s;}
    inline bool is_done(){return done;}
    inline void set_done(bool state){done=state;}
  private:          // these data items are for each vertex
    string vertex;  // name of this vertex
    int distance;   // distance of this vertex from source vertex
    bool done;      // finished with this vertex
                    // other variables could go here for more applications
    Adj edge_list;  // the head of the edge list for this vertex
};
typedef vector<my_vertex> graph_type; // type of the graph object

typedef graph_type::iterator graph_iterator; // type of iterator for vector

// probably best if the above was in a header file and #include here

int main(int argc, char *argv[])       // standard main program definition
{

  fstream my_io;          // a users variable that holds stream information
  string vert1;           // just a place for inputting
  string vert2;           // "
  int    weight;          // "
  
  graph_type G;           // The graph is stored in the object G
  graph_iterator vert;    // need an iterator through G
  graph_iterator vert_p2; // another iterator through G
  Adj temp;               // need a temporary Adj list
  Adj::iterator p;        // need a temporary iterator through temp
  bool found;             // temporary
  
  cout << "graph_read running" << endl; //trace execution 
  
  if(argc<2) // be sure user supplied a file name
  {
    cout << "no file on command line. Exiting." << endl;
    return 0;
  }
  
  cout << "about to open " << argv[1] << endl; // just informative
  
  my_io.open(argv[1], ios::in);                 // open file
  if(!my_io)                           // believe it!, anything can go wrong
  {
    cout << "can not open file " << argv[1] << endl;  // common error message
    return 1;                                         // common error exit
  }
  while(!my_io.eof()) // loop until end of file
  {
    my_io >> vert1;
    if(vert1==string("#") || vert1==string("") ) // ignore comments and empty
    {
      my_io.ignore(100, '\n');
      continue;
    }
    my_io >> vert2 >> weight; // read the other two fields on the line
    cout << vert1 << "  " << vert2 << "  " << weight << endl; //print what read
    // if vert1 not a vertex, construct it
    if(G.empty()) // special 
    {
      G.push_back(my_vertex(vert1, 0, my_edge(vert2, weight)));
      G.push_back(my_vertex(vert2, 10000));  // no Adj yet
    }
    else
    {
      // find and push_back if vert==G.end() for vert2 and vert1
      found = false; // look for vert2 in vector of vertices
      for(vert=G.begin(); vert!=G.end(); vert++)
      {
        if(vert->exist(vert2))
    	{
    	  found = true;
    	  break;
    	}
      }
      if(!found)  
      {
        // if vert2 not a vertex, construct it
	G.push_back(my_vertex(vert2, 10000)); // no Adj
      }
      found = false; // look for vert1 in vector of vertices
      for(vert=G.begin(); vert!=G.end(); vert++)
      {
	if(vert->exist(vert1))
	{
          vert->add_edge(vert2, weight);
	  found = true;
	  break;
	}
      }
      if(!found)  
      {
        // if vert1 not a vertex, construct it
	G.push_back(my_vertex(vert1, 10000,
		    my_edge(vert2, weight)));
      }
    } // end if for 'else'
  } // end of while not eof
  
  // OK, G vector will not be reallocated again,
  // fill in Adj to vector pointers
  for(vert=G.begin(); vert!=G.end(); vert++)
  {
    for(p=vert->get_Adj_ptr()->begin();
       p!=vert->get_Adj_ptr()->end(); p++)
    {
      vert2 = p->get_edge();
      for(vert_p2=G.begin(); vert_p2!=G.end(); vert_p2++)
      {
        if(vert_p2->exist(vert2))
	{
	  p->set_vertex_ptr(vert_p2);
          break;
	}
      }
    }
  }
  
  cout << "print out data structure G" << endl;
  cout << "G.begin(), G.end() " << G.begin() << ' '
       << G.end() << endl;
  for(vert=G.begin(); vert!=G.end(); vert++)
  {
    cout << vert->get_vertex() << " " << vert->get_distance()
	 << "  " << vert << endl;
    temp = vert->get_edges();
    cout << "        ";  // use begin()  end() to be safe 
      for(p=temp.begin(); p!=temp.end(); p++)
      cout << '(' << p->get_edge() << ','
	   << p->get_vertex_ptr() 
	   << ',' << p->get_weight() << ") ";
      cout << endl;  
  }
  
  my_io.close();              // just being neat, closing file being read
  cout << "graph_read finished" << endl;
  return 0;
}

NOTE:  works around bug in  in g++ on gl
       g++ -o graph_read1 graph_read1.cc
       graph_read1 < dijkstra1.dat

// graph_read1.cc    graph_read1 < dijkstra1.dat > dijkstra1.chk
//                   work around for g++ on UMBC9 (IRIX 6.5.3)
//                    segmentation fault on my_io >> vert1 problem

#include <iostream>                    // console I/O
#include <string>                      // for input data
#include <algorithm>
#include <list>
#include <vector>
using namespace std;                   // bring standard names into scope

class my_vertex;  // just declaring my_vertex as a type

class my_edge     // the type of objects in the adjacency lists
{
  public:
    my_edge(string Aedge, int Aweight){edge=Aedge; weight=Aweight;}
    inline string get_edge(){ return edge;}
    inline my_vertex * get_vertex_ptr(){return vertex;}
    inline void set_vertex_ptr(my_vertex * ptr){vertex=ptr;}
    inline int get_weight(){ return weight;}
  private:         // these data items are for the adjacency list
    string edge;   // name of vertex (edge is from parent vertex to here)
    my_vertex * vertex; // pointer to this vertex in the vector
    int weight;    // weight of this edge
};
typedef list<my_edge> Adj;  // the type of each adjacency list
  
class my_vertex   // the type of objects in the vector of vertices
{
  public:
    my_vertex(string Avertex, int d)
              {vertex=Avertex; distance=d;}
    my_vertex(string Avertex, int d, my_edge Aedge)
              {vertex=Avertex; distance=d; edge_list.push_back(Aedge);}
    inline int get_distance(){return distance;}
    inline string get_vertex(){return vertex;}
    inline Adj get_edges(){return edge_list;}
    inline Adj * get_Adj_ptr(){return &edge_list;}
    inline void add_edge(string vert2, int weight)
                         {edge_list.push_back(my_edge(vert2, weight));}
    inline void set_distance(int d){distance=d;}
    inline bool exist(string s){return vertex==s;}
    inline bool is_done(){return done;}
    inline void set_done(bool state){done=state;}
  private:          // these data items are for each vertex
    string vertex;  // name of this vertex
    int distance;   // distance of this vertex from source vertex
    bool done;      // finished with this vertex
                    // other variables could go here for more applications
    Adj edge_list;  // the head of the edge list for this vertex
};
typedef vector<my_vertex> graph_type; // type of the graph object

typedef graph_type::iterator graph_iterator; // type of iterator for vector

// probably best if the above was in a header file and #include here

int main(int argc, char *argv[])       // standard main program definition
{

  string vert1;           // just a place for inputting
  string vert2;           // "
  int    weight;          // "
  
  graph_type G;           // The graph is stored in the object G
  graph_iterator vert;    // need an iterator through G
  graph_iterator vert_p2; // another iterator through G
  Adj temp;               // need a temporary Adj list
  Adj::iterator p;        // need a temporary iterator through temp
  bool found;             // temporary
  
  cout << "graph_read1 running" << endl; //trace execution 
  

  while(!cin.eof()) // loop until end of file
  {
    cin >> vert1;
    if(vert1==string("#") || vert1==string("") ) // ignore comments and empty
    {
      cin.ignore(100, '\n');
      continue;
    }
    cin >> vert2 >> weight; // read the other two fields on the line
    cout << vert1 << "  " << vert2 << "  " << weight << endl; //print what read
    // if vert1 not a vertex, construct it
    if(G.empty()) // special 
    {
      G.push_back(my_vertex(vert1, 0, my_edge(vert2, weight)));
      G.push_back(my_vertex(vert2, 10000));  // no Adj yet
    }
    else
    {
      // find and push_back if vert==G.end() for vert2 and vert1
      found = false; // look for vert2 in vector of vertices
      for(vert=G.begin(); vert!=G.end(); vert++)
      {
        if(vert->exist(vert2))
    	{
    	  found = true;
    	  break;
    	}
      }
      if(!found)  
      {
        // if vert2 not a vertex, construct it
	G.push_back(my_vertex(vert2, 10000)); // no Adj
      }
      found = false; // look for vert1 in vector of vertices
      for(vert=G.begin(); vert!=G.end(); vert++)
      {
	if(vert->exist(vert1))
	{
          vert->add_edge(vert2, weight);
	  found = true;
	  break;
	}
      }
      if(!found)  
      {
        // if vert1 not a vertex, construct it
	G.push_back(my_vertex(vert1, 10000,
		    my_edge(vert2, weight)));
      }
    } // end if for 'else'
  } // end of while not eof
  
  // OK, G vector will not be reallocated again,
  // fill in Adj to vector pointers
  for(vert=G.begin(); vert!=G.end(); vert++)
  {
    for(p=vert->get_Adj_ptr()->begin();
       p!=vert->get_Adj_ptr()->end(); p++)
    {
      vert2 = p->get_edge();
      for(vert_p2=G.begin(); vert_p2!=G.end(); vert_p2++)
      {
        if(vert_p2->exist(vert2))
	{
	  p->set_vertex_ptr(vert_p2);
          break;
	}
      }
    }
  }
  
  cout << "print out data structure G" << endl;
  cout << "G.begin(), G.end() " << G.begin() << ' '
       << G.end() << endl;
  for(vert=G.begin(); vert!=G.end(); vert++)
  {
    cout << vert->get_vertex() << " " << vert->get_distance()
	 << "  " << vert << endl;
    temp = vert->get_edges();
    cout << "        ";  // use begin()  end() to be safe 
      for(p=temp.begin(); p!=temp.end(); p++)
      cout << '(' << p->get_edge() << ','
	   << p->get_vertex_ptr() 
	   << ',' << p->get_weight() << ") ";
      cout << endl;  
  }

  // PUT PROJECT CODE HERE

  cout << "graph_read1 finished" << endl;
  return 0;
}


Last updated 4/7/99