Graphs, Dijkstra's shortest path algorithm

Graphs have vertices and edges. Each edge connects two vertices.

Sometimes graphs are directed (an edge is an ordered pair, (a,b), representing a link from a to b, and not vice versa). Sometimes graphs are undirected ( where an unordered pair, written either (a,b) or (b,a), denotes a bidirectional edge). Graphs may be weighted, with a number, a weight, associated with each edge.

We will stick to directed, weighted graphs. When we want to consider unweighted graphs, we'll just set the weights to 1's.

A path of length k in a graph is a sequence of k+1 vertices, v0, v1, v2, ... vk, such that each ( vi, vi+1) is an edge for i in 0..k-1.
A path is simple if no two vi's are the same.
A path is a cycle if vk = v0.
A graph is connected if there is a path between every pair of vertices.



A graph representation:
// Some useful type names and types
typedef int Vertex;
typedef float Weight;
class NbrNode { public:
    Vertex v; 
    Weight w;
    NbrNode *next;
    NbrNode(int vv, float ww, NbrNode *nn){v = vv; w = ww, next = nn; }
};
typedef NbrNode *List;

class Graph { public:
    // data
    int V; // number of vertices
    int E; // number of edges
    List* adj; // array such that adj[u] is singly linked list of nbrs of u.
    List nil = 0;

    // functions
    void read(string filename){
        ifstream infile(filename);
        // read first line to get sizes, set up empty adj array
        infile >> V >> E; 
        adj = new List[V];
        for (int i = 0; i < V; ++i) adj[i] = nil;

        // read subsequent lines, each giving one edge. 
        Vertex u, v; Weight w;
        for (int i = 0; i < E; ++i) {
            infile >> u >> v >> w;
            adj[u] = new NbrNode(v, w, adj[u]);
        }
    }
};











What is the file representation of this graph?
1 --> 2 --> 3 
A.
2 3
1 2 1
2 3 1
B.
3 2
3 2 1
2 1 1
C.
2 3
1 2 1
2 3 1
D.
2 3
3 2 1
2 1 1











Draw this graph and show it's representation.
3 4
0 1 1
1 2 1
2 0 1
2 1 1



How can one determine the shortest path between vertex s and vertex d in an unweighted graph? (s is for "source", d for "destination".)

It turns out the best general method is to learn the shortest path from s to every vertex in the graph. This is called the single source shortest path, SSSP, problem.


When the graph is unweighted a breadth first traversal starting at s can establish the distances.
void BFS(Graph G, Vertex s, int *d) { 
// d is an array of G.V ints.  
// For each vertex v make d[v] the length of the shortest path from s to v.
Queue Q; 
for (Vertex u = 0; u < G.V; ++u) 
    d[u] = inf;  // inf can be V
d[s] = 0;
Q.enqueue(s);

while (Q.size() > 0) do {
    Vertex u = Q.dequeue();

    // For each neighbor v of u we do the if-stmt.
    for (List p = adj[u]; p != nil; p = p->next) {
        Vertex v = p->v;
        if (d[v] == inf) { 
             d[v] = d[u] + 1; 
             Q.enqueue(v); }
    }
}
Let's see it again with invariants stated. The invariants help us verify the correctness of the algorithm.
 1  void BFS(Graph G, Vertex s, int *d) { 
 2  // d is an array of G.V ints.  
 3  // For each vertex v make d[v] the length of the shortest path from s to v.
 4  Queue Q; 
 5  for (Vertex u = 0; u < G.V; ++u) 
 6      d[u] = inf;  // inf can be V
 7  d[s] = 0;
 8  Q.enqueue(s);
 9  /* Invariants:  
10     Every vertex v that has d[v] < inf has d[v] = correct distance to s.
11     Every vertex v that is in Q or has been in Q has d[v] < inf.
12  */
13  while (Q.size() > 0) do {
14      Vertex u = Q.dequeue();
15  
16      // For each neighbor v of u we do the if-stmt.
17      for (List p = adj[u]; p != nil; p = p->next) {
18          Vertex v = p->v;
19          if (d[v] == inf) { 
20               d[v] = d[u] + 1; 
21               Q.enqueue(v); }
22      }
23  }
24  /* Upon exit of the while loop, every vertex reachable from s has been in Q.
25     Ergo array d contains correct distances to s.

Analysis: For a connected graph, the runtime of BFS is O(E);





Weighted Graph Example (from CLRS)
5 9
0 1 10
1 4 5
1 4 2
2 3 4
3 0 7
3 2 8
4 1 3
4 2 9
4 3 2

When the graph is weighted, the straightforward BFS won't solve the shortest path problem. However a similar strategy using a priority queue instead of a FIFO queue works. It is Dijkstra's algorithm.

void DijkstraSSSP(Graph G, Vertex s, int *d) { 
// G is weighted.  d is an array of G.V ints.  
// Result: For each vertex v, make d[v] the length of the shortest path from s to v.
    BinaryHeap PQ; 
    bool *done = new bool[G.V]; // array to mark finished (we know shortest path) vertices.
    for (Vertex u = 0; u < G.V; ++u) {
        d[u] = inf;  // inf can be V
        done[u] = false;
        PQ.add(u);
    }
    d[s] = 0; PQ.decreaseKey(s);
    while (PQ.size() > 0) do {
        Vertex u = PQ.extractMin();
        done[u] = true;

        // For each neighbor v of u we do the if-stmt.
        for (List p = adj[u]; p != nil; p = p->next) {
            Vertex v = p->v; Weight w = p->w;

            if (done[v] == false and d[u] + w < d[v]) { 
                 d[v] = d[u] + w; 
                 // fix heap property because of v's new priority. 
                 PQ.decreaseKey(v); 
            }
        }
    }
}
Let's see it again with invariants stated. The invariants help us verify the correctness of the algorithm.

A vertex "color" convention is often used to understand Dijkstra's alg.
black = done = we know the shortest path length to done vertices.
gray = fringe = we know a path length, not necessarily the shortest.
white = far = we've learned nothing about this vertex yet.
 1  void DijkstraSSSP(Graph G, Vertex s, int *d) { 
    // G is weighted.  d is an array of G.V ints.  
    // For each vertex v make d[v] the length of the shortest path from s to v.
 2    BinaryHeap PQ; 
 3    bool *done = new bool[G.V]; // array to mark finished (we know shortest path) vertices.
 4    for (Vertex u = 0; u < G.V; ++u) {
 5      d[u] = inf;  // inf can be V
 6      done[u] = false;
 7      PQ.add(u);
      }
 8    d[s] = 0; 
      /* Invariants:
          (black) For done vertex v, d[v] is the shortest path length.
          (gray) For fringe vertex v (i.e. d[v] < inf) we know 
            the shortest path via the done vertices,
          but possibly there is a shorter path using some other 
            (white or gray) vertices as well.
      */
10    while (PQ.size() > 0) do {
11      Vertex u = PQ.extractMin();
12      done[u] = true;
    
13      // For each neighbor v of u we do the if-stmt.
14      for (List p = G.adj[u]; p != nil; p = p->next) {
15          Vertex v = p->v; Weight w = p->w;
    
16          if (done[v] == false and d[u] + w < d[v]) { 
17               d[v] = d[u] + w; 
                 // fix heap property because of v's new priority. 
18               PQ.decreaseKey(v); 
            }
        }
    // Algorithm is correct after the while loop because all vertices are in the done category.
     }
2  }
Analysis: For a connected graph, the runtime of DijkstraSSSP is O(E*log(V));