Single Source, Shortest Path Algorithms


(This all assumes that we're given a graph G = (V, E) and a weight function w : e → R)

Shortest Path for Unweighted Graphs

For unweighted graphs we can just use BFS to find the shortest path from the source node to any other node.

Shortest Path for Weighted Graphs

Problem: Given G = (V, E) and src ∈V, find a shortest path p (from src to v, ∀v ∈V)

A couple of important notes:

  1. Optimal Substructure:
    A shortest path from src to v is composed of subpaths that are also the shortest subpath.
    Given a shortest path: pick any point on it (let's name it t).  The path from src to t is a shortest path from src to t.
    1. This is important because both greedy algorithms and dynamic algorithms exploit this optimal substructure property to to come up with good, correct answers.
  2. Negative weight edges
    In some cases a graph may contain negative edge weights (for example, if the edges represent financial actions, then getting (or giving) money might be represented with negative edge weights.
    In some cases this might not make sense (e.g., when edges represent roads on a map)
  3. Shortest paths never contain cycles
    If a graph has a cycle with a net negative weight then the shortest path is undefined (because you can keep going around that cycle, and thus reducing the total each time).
    If the graph has a cycle with a net positive weight it would be pointless to follow it (because doing so will increase the total but will not get you any closer to the destination vertex)

Pseudocode - Dijkstra's

<See notes from the  prior lecture>

Note that Dijkstra's algorithm cannot handle negative weight cycles (in contrast to the Bellman-Ford Algorithm, below)

Pseudocode - Bellman-Ford

First, let's define the 'init' method:

Θ( |V| ) Init(G, s)
    s.dist = 0
    create an array named distances, whose length is |V| 
        // This will keep track of the current, known, shortest path distance from the source vertex to any other vertex
        fill distances with \( \infty \)
        change the slot corresponding to the source vertex to be zero
    foreach v ∈ V:
       v.dist = \( \infty \)
       v.parent = null


The 'Relax' method will check if the distance from v1 to v2 is a shorter way to get to v2, and if so it will update v2 so that v2's parent/predecessor points back to v1 (and the distance is updated appropriately).

Θ( 1 )

Relax( Vertex possShortCut, Vertex dest)
    maybeShorter = distances[possShortCut] + weight(possShortCut, dest)
    if maybeShorter < distances[dest]
        distances[dest] = maybeShorter
        dest.dist = maybeShorter
        dest.parent = possShortCut

In the picture below,
'j' is e.destination in the above code
'i' is the current predecessor in the graph before 'j'
'k' is e.start in the above code

Note: Infinity + x is still infinity
The Bellman-Ford algorithm will go through and repeatedly relax all the edges, checking (over and over) to see if there's a shorter path between any of the vertices.

Θ( |V| ) Bellman-Ford(Graph G, Vertex src)
   Init(G, src)
Θ(|V| * |E|)    for( i = 1; i ≤ |G.V| - 1; i++ ) // note that we're just counting here - we need to do the inner loop |G.V| times
      // think of this as 'best route after 1 hop', then after 2 hops, etc
      foreach edge e ∈ G.E  // go through all the edges in the graph.  _ALL_ of them
         Relax(e.start, e.destination) // starting from src can we use e.start as a shortcut to get to e.dest?
Θ(|E|)  // lastly, check for negative-weight cycles: 
   foreach edge e ∈ G.E // e goes from u to v
        if( v.dist > u.dist + w(u,v)
            return false;
Θ( 1 )  :)  //No negative weight cycles
   return true