CS35 Lab 11: Graphs

Due by 11:59 p.m., Tuesday, December 8, 2014

This lab will have you and your lab partner implement a graph representation, and several graph algorithms.

Getting Started

As usual, you can get the initial files for this lab from Github. The new files are listed here, and the files that you will need to edit are highlighted in blue:

Note: We've provided you with binaryHeap and HashTable implementations, but you're free to use your own if you like. If you do use your own implementations, you will need to add your HashTable implementation from last week into your library. That is, you should copy hashtable-inl.h to cs35/labs/11/library/. You'll also need your BinaryHeap implementation to solve Dijkstra's Algorithm.

You are responsible for the following deliverables:

  1. Finish the implementation of Graph
  2. Finish the implementation of UGraph
  3. Implement graph algorithms (e.g., BFS, DFS, Dijkstras, Topological Sort) in graph-algorithms-inl.h
  4. Add tests in testGraph to ensure that Graph, UGraph, and each graph algorithm works correctly

The Graph and UGraph class

The only data member for Graph has been provided: an adjacency list that maps from vertices to outgoing edges. Graph represents graphs that are weighted and directed. An edge is described by the Edge class, which stores a label to identify the edge (e.g., the flight number of the edge represents airline routes), a source vertex, a destination vertex, and a weight (uniform for unweighted graphs).

You will implement all the provided methods on the Graph class, and be sure to test them thoroughly.


As mentioned, the Graph class represents graphs as being weighted and directed. This is the most general representation of a graph: unweighted graphs can be simulated by simply ignoring the edge weights (i.e., assigning the same cost to each). Similarly, we can simulate an undirected graph by simply ensuring each inserted edge has a complement. That is, for every edge A-->B, there must be a B-->A with matching weight and label.

We could simply rely on a user of the Graph class to maintain this property, but it would be better to write a new class that encapsulates this behavior.

In ugraph.h, you will find the declaration for UGraph, an undirected graph class. This class inherits most of its features from the Graph class. In fact, most of the methods will remain unchanged since their behavior does not depend on whether the graph is directed or undirected. Only three methods need to be over-ridden - insertEdge, removeEdge, and getEdges. You must implement removeEdge and getEdges. You are also responsible for understanding this fact: our use of inheritance and polymorphism allows us to write graph methods (e.g., Dijkstras, breadth-first search) that work the same regardless of whether we are working with a directed or undirected graph.

Note: You can call the parent class implementation of a method by using the scope operator (e.g., Graph<V,E,W>::). For example, in the provide insertEdge, we simply add two directed edges using the Graph implementation of insertEdge. If we omitted the scope operator, C++ would call the UGraph implementation, leading to infinite recursion. removeEdge will be as simple as insertEdge (i.e., simply call the parent method twice). getEdges will be slightly more complicated: you must call the parent method, but ensure that each edge appears only once in the vector (i.e., only one of A-->B or B-->A should be in the list since they are the same exact edge).

Implementing the graph algorithms

Please complete each function declared in graph-algorithms.h to ensure you understand three traversal algorithms we discussed in class - depth-first search, bread-first search, and Dijkstra's. You must test each function you implement using the testGraph program. I suggest starting with simple graphs we drew in class, but you should also come up with much larger examples to ensure that you cover interesting cases.

Feel free to add additional methods, although you will need to ensure you add their declaration in the header file. At a minimum, your functions are:

Note: we'll see Dijkstra's Algorithm and Topological Sort in detail in class this week.

Recursive DFS

We've talked about using a stack to implement DFS, but we could also do so using recursion, letting the function stack act as our stack. Below is pseudocode for one possible recursive DFS implementation.
reachableDFS(G, source, dest) :
  dictionary VISITED, each vertex is false

  toReturn = recursiveDFS(G,source, dest, VISITED)
  return toReturn

recursiveDFS(G, current, dest, VISITED)
  if current == dest
   return true
  mark current as visited
  for each neighbor w of current
    if w not visited
      if recursiveDFS(g, w, dest, visited)
       return true
  return false


A summary of dijkstra's algorithm is below for reference.

Topological Sort

Here is pseudocode for a topological sort algorithm; we'll talk about it more in class on Thursday. The underlying idea is to “count down” the number of incoming edges into each vertex. When each incoming edge is accounted for, the vertex has no remaining dependencies and can be output. You need to adapt the “output” to make sense for the given signature of topsort in your graph algorithm.

  depend = new hashtable
  S = new stack
  for each v in V
    depend[v] = count-in-edges(v)
    if depend[v] == 0

    u = S.pop()
    output u
    for each (u,v) in E
      depend[v] = depend[v] - 1
      if depend[v] == 0

Some helpful hints


As an example, you could construct a graph to represent, say, the search problem of trying to get from Parrish Hall to The Ville. If you do this correctly, your test may look like this (with added print statements).

$ ./testGraph 
Let's build a map of Swarthmore!

The graph: 
Softball: {Ville, Soccer, Tunnel, Train}
Bridge: {Tennis, Wharton}
Sharples: {Clothier, Tunnel}
Soccer: {Softball, Tennis}
Wharton: {Bridge, Clothier}
Magill: {Parrish, Train}
Tennis: {Soccer, Bridge, Tunnel}
Parrish: {Magill, Clothier}
Train: {Magill, Ville, Softball}
Ville: {Train, Softball}
Tunnel: {Softball, Sharples, Tennis}
Clothier: {Wharton, Parrish, Sharples}

Can I get from Parrish to the Ville?

DFS: yes!

How do I get from Parrish to the Ville?

Using BFS:

The shortest path length is: 3

Other examples to try are maze inputs expressed as graphs, flight plan examples from class, a map of Crum woods, a map of your hometown, a partial map of your Facebook friends...

You might find the following hints more or less helpful: