APSP Floyd-Warshall
Problem
Given a directed weighted graph (with no negative weight cycles), we want to be able to answer queries of the form “what is the minimum distance between and ?” quickly. This is a classic All Pairs Shortest Path Algorithm.
Attempt 1
Do not pre-process the graph at all. Run Dijkstra each time you get a query (if the query has the same source vertex as a previous query, no need to run Dijsktra again).
Then, to answer queries , it would take . In case of a dense graph, this becomes
Floyd-Warshall (FW)
Floyd and Warshall gave a beautifully simple algorithm to calculate APSP in time. So, it is better to use FW rather than repeatedly running Dijsktra in case of a dense graph. In case of a sparse graph, running Dijkstra times is faster than FW since grows slower than . In fact, in case of a sparse graph, we don’t know how to solve APSP faster than .
Notice that if all the edge weights were identical, we could simply run BFS/DFS from each node and get the APSP in time.
FW is also useful for finding the diameter of a graph (since diameter involves knowing the shortest path between all pairs of nodes, and then taking the maximum one)
FW outputs to be the shortest distance from to , for all pairs of vertices .
Why can we use DP here? Well, shortest paths have an amazing amount of optimal sub-structure: We know that if is the shortest path from to and it contains , then contains the shortest path from to and from to So, a shortest path between two nodes is composed of the shortest path between many intermediary nodes too.
Moreover, (and more importantly) many shortest path calculations depend on the same sub-pieces. That is, they have overlapping subproblems!
Although it is easy to understand and believe that shortest paths have overlapping subproblems, it is much more difficult to correctly identify these right subproblems?
This is where the genius of Floyd and Warshall truly shines:
Let be the shortest path from to that only uses intermediate nodes (if any) from the set . In other words, is the length of the shortest path from to that does not include any vertices not in .
Let be the weight of an edge from to .
Then, our base case is: (where represents the empty set). That is, if you cannot use any intermediate nodes, the only way to get from to is through a direct edge, if any. If no such edge exists, .
But the problem now is that there are so many possible such sets . In particular, there are possible subsets of that are all candidates and possible subproblems that we need to solve. But the question is, do we really need to solve all of them? Or can we only choose a few to solve and still guarantee that we get the correct answer.
This step represents another flair of creativity and intelligence by Floyd and Warshall. They realised that they only needed to solve subproblems (where ). In particular, only the following sets need to be considered as subproblems:
At each step, as we grow our set , we are allowed to use more nodes as intermediate nodes if necessary. So, in the last step, we are allowed to use all possible nodes as intermediate nodes, giving us the correct shortest paths.
How do we obtain the recurrence relation now, using the pre-calculated subproblems? Say, we are trying to calculate and we have already calculated all the shortest paths when we are allowed to use nodes in as intermediaries.
Then, there are two possibilities:
The shortest path in includes 8 (the new shortest path contains 8 as an intermediate node)
Since is an intermediate node along the shortest path from to using only the nodes in , the shortest distance from to along this new path must be
We cannot have any other nodes like in this path from to or from to since they do not appear in the overall path from to using .
The shortest path in does not include 8 (that is, even though we are allowed to use , it does not give us a shorter path)
So, the recurrence relation is .
Optimising Space
Notice that we don’t actually need to store a 3-D matrix for S. To calculate the APSP using intermediate nodes in we only need the values from . So, a single 2-D matrix suffices as we store the current value and keep over-writing the old values, updating as and when necessary. Notice that it can never be the case that the shortest path from to includes as an intermediate node (as that would indicate a negative cycle containing ). So we don’t need to worry about some values already using while others using while we are adding to the set of allowed intermediate nodes as we progressively update the dp table.
Pseudocode
In the above code, the outer loop tracks which nodes are allowed to be used (the current value of determines ). The inner 2 loops, deal with looking at every pair of nodes. We return a 2-D matrix where is the shortest path from node to .
Merely glancing at the 3 nested for loops should convince you that the running time of FW is
In fact, FW is so elegant that it only needs 4 lines of code to express the key algorithm: 3 of which are for-loops, and one represents the sheer brilliance of the two computer scientists.
Note that there are subproblems in FW: for each node (there are nodes), you need to find the shortest path to all other nodes (again nodes) using sets of allowed intermediate nodes. So, = . Another way to think about this is that we were originally using a 3-D matrix to store all our subproblems but now we are overwriting entries in a 2-D matrix (since we don’t need to store older values). In particular, each entry in the 2-D matrix is overwritten exactly times (once for each iteration of the outermost loop) and since the size of the matrix is , there must be subproblems. Using this, we also observe that all our time is spent filling this table and it takes is to fill up a cell in the memo table since we are just calculating the minimum of two values. So, the running time is .
Path Reconstruction
What if you want the path, along with the path length between any two nodes?
There is also an optimal substructure for this! If is the first hop on the shortest path from to , then the shortest path from to is shortest path from to .
So, rather than storing the entire path, it suffices to store just the first hop for each destination (e.g. routing table) and you can reconstruct the path between any pair of nodes.
You only need space for this. For each pair of nodes, store the first node in the path. That is, stores the node that is 1 hop away from in the path from to . Then, to reconstruct the path, keep looking at those 1-hop away nodes till you reach .
For example, in the above diagram, . Then look at to determine the next node. And so on.
Actually, there is no reason we need to even store the “first” node. You can store any node (say, ) along the shortest path from to and then recursively find the shortest path from to and to . In FW, you can store this intermediate node each time you modify/update the matrix entry for a pair.
Variants of FW
Transitive closure - Return a matrix where if there exists a path from to ; otherwise
Minimum bottleneck edge - For , the bottleneck edge is the heaviest edge on a path between and . Return a matrix where = weight of the minimum bottleneck along to .
Last updated