Problem: Given a directed, weighted, simple, and connected graph, how can we find the shortest path between any two pairs of vertices?

Intuition

Consider the following graph.

graph LR
1 -. 20 .-> 2;
3 -. 80 .-> 2;
2 -. 10 .-> 3;
3 -. 40 .-> 1;

We can find the shortest path between any two pairs of vertices, by starting from all known edges, and working our way to our shortest path by allowing “intermediate” vertices one by one! See below to see what we mean.

First, we build adjacency matrix for our graph, defined as

While this matrix stores the edges and their weights in the graph, we can alternatively think of it as storing the weight of the shortest path from , allowing no intermediate vertices (i.e. paths of only 1 edge). We can then let each vertex be intermediate vertices, and build our matrix to obtain the shortest paths between any two pairs of nodes.

Now, let’s do the following:

  1. Let 1 be an intermediate vertex. Then, this changes the shortest path from to 60, because we can traverse through 1. More formally, we update because
    We then update our matrix, giving us a matrix whose yields the shortest paths allowing 1 as an intermediate vertex! We’ll call this the pass by 1 matrix.
  2. Let’s repeat this process, now allowing 2 to be an intermediate vertex. Now the shortest path from actually appears, as we can take 2 to go to 3. More formally,
    We again update our matrix, giving us a matrix whose yields the shortest paths allowing 1 and 2 as intermediate vertices. We’ll call this the pass by 2 matrix.
  3. We repeat this with 3, allowing 3 to be an intermediate vertex. Now, the shortest path from appears, as we cant take 3 to go to 1. More formally,
    Updating our matrix, we get a matrix whose yields the shortest paths allowing 1,2, and 3 all as intermediate vertices. We’ll call this the pass by 3 matrix.

Now, because we’ve allowed all vertices as intermediate vertices, our matrix yields the lengths of all shortest paths! We now define another matrix to find the actual shortest path between vertices.

Define matrix such that

We will use this matrix to tell us the direct predecessor of in the shortest path from . Currently, it allows no intermediate vertices.

We’ll process this graph in a similar way to .

  1. Let 1 be an intermediate vertex. Then, we see that the shortest path from changes because instead of taking the path , we can instead take . In other words,
    So , because the direct predecessor in this path is the same as the direct predecessor in the path , which is 1. While this process may seem redundant, it paves the way for an algorithm later!
  2. Let 2 be an intermediate vertex. Then, we see that the path opens because we can take the path . In other words, becaus ,
    So as the direct predecessor of in our new path is intermediate vertex 2.
  3. Let 3 be an intermediate vertex. THen, we see that the shortest path from changes as instead of taking the path , we can instead take path . In other words,
    So , because the direct predecessor of 1 in this new path is now intermediate vertex 3.

We are done! Now, our stores the direct predecessor of in the shortest path from , allowing all intermediate vertices! To reconstruct a path, we start at a cell, and work backwards.

For example, suppose we wanted to know the path from 2 to 1. Then, we see that:

  • Because , the shortest path from 2 to 1 has direct predecessor 3.
  • Furthermore, because we know that there are no other nodes in shortest path, because our starting node is equal to our predecessor.

This gives us shortest path

Floyd’s Algorithm

Pseudocode

Let’s now implement this algorithm in code. While we defined and created two arrays separately above, we can actually create both of them at the same time in code!

Let graph have vertices. Then, we have the following pseudocode:

def floyds(graph):
    A = adjacency_matrix(graph) # V x V
    P = predecessor_matrix(graph) # V x V
 
    # iterate through all intermediate vertices
    for intermediate in vertices(graph): 
        # iterate through all possible paths
        for start in vertices(graph): # choose start of path
            for end in vertices(graph): # choose end of path
                # see if taking the intermediate yields a shorter path
                if A[start][intermediate] + A[end][intermediate] < A[start][end]:
                   # update weight
                   A[start][end] = A[start][intermediate] + A[end][intermediate]
                   # update predecessor
                   P[start][end] = P[intermediate][end] 
 
    return P

Time Complexity

Let’s analyze this algorithm for its time complexity.

  • We see that our array initialization on lines and both take time.
  • Furthermore, the loops on lines all take time, giving us complexity .

This gives us total time complexity