## 35^{th} Friday Fun Session – 29^{th} Sep 2017

Floyd-Warshall, also known as Roy-Warshall is an All-Pairs Shortest Path (APSP) algorithm developed by Robert Floyd, Bernard Roy, and Stephen Warshall. It is an example of dynamic programming that uses 3 nested loops. At a cost *O(|V| ^{3})*, it is quite impressive, given that Bellman-Ford might encounter the same cost (

*O(|V||E|)*) to find only Single Source Shortest Path (SSSP) for dense graph having

*|V|*edges. Floyd-Warshall can work with negative edges just like Bellman-Ford. After all, both are based on dynamic programming. As for detecting negative cycle, once again, both can detect it. However, in presence of negative cycle, results from both are invalid.

^{2 }### Three Nested Loops

dist[][] //shortest path matrix p[][] //predecessor matrix, used to reconstruct the path dist[][] = ∞ for each vertexidist[i][i] = 0 for each edge (i,j) dist[i][j] = weight(i,j) p[i][j] =jfork= 1 to |V| fori= 1 to |V| forj= 1 to |V| if dist[i][j] > dist[i][k] + dist[k][j] dist[i][j] = dist[i][k] + dist[k][j] p[i][j] = p[i][k]

To compute the shortest path between any pair (*s*, *t*), we have considered each of the |V| vertices as intermediate points *k*, and chosen the cheaper between *i)* existing (*s*, *t*) and *ii)* the sum of *s* to *k* and then from *k* to *t, *meaning* s *to* t *via* k*.

### Short-circuiting an SSSP?

Does it mean that we can derive a SSSP solution for any pair (s, t), at a cost of *O(|V| ^{2})*? To be precise, can we do the following?

fork= 1 to |V| if dist[i][j] > dist[i][k] + dist[k][j] dist[i][j] = dist[i][k] + dist[k][j]

After all, we have *relaxed* via all the intermediate nodes. Well, that will not work! Why?

### Dynamic Programming

If we want to get the shortest path between (*i*, *j*) using *k* (1 to *k*) intermediate nodes then we have to choose the cheaper between the below paths:

- Without using
*k*: dist[*i*][*j*] using intermediate nodes 1 to*k*-1. - Using
*k*: dist[*i*][*k*] + dist[*k*][*j*], where both dist[*i*][*k*] and dist[*j*][*k*] should*make use of intermediate nodes 1 to k-1*.

At *k* = 0, dist[][] is initialized using edge weights where exists, 0 for diagonals (dist[*v*][*v*]) and infinite for the rests.

### An Example

Suppose, we want to compute dist[2][3] when *k* = 5.

Then, dist[2][3] = min { dist[2][3], dist[2][5] + dist[5][3] }

Here, all three distances – dist[2][3], dist[2][5] and dist[5][3] must already use intermediate nodes 1 to 4. Meaning, dist[2][5] is not the static cost set at *k*=0; possibly the edge cost, 0 or infinite. Rather, dist[2][5] is already computed using *k* from 1 to 4. Similarly, dist[5][3] (and dist[2][3] as well) is also computed using *k* from 1 to 4.

In other words, we cannot compute a certain dist[*s*][*t*] alone, using the intermediate nodes 1 to *k*. Rather for each intermediate node *k,* we need to compute dist[*i*][*j*] progressively, using the 3 nested loops, as shown earlier.

Obviously we can use recursion without the loops. That will not save any work for us. In fact, while using recursion, if we are not reusing existing solutions for the sub-problems, we will repeat the computation – something very expensive.

### Path Reconstruction

The predecessor matrix *p, *keeps track of the shortest path. If we have to find the best path from *s* to *t*, we know for sure that we start with *s*. We print *s*. To know where we went from there, we have to look at p[*s*][*t*]. If that is *t*, we are done as that is the destination. However, if that is not the case, that means we find another node *r.* Then we know from *s* we went to an intermediate node *r*. So this becomes the new start *s* for the rest of the path. However, destination remains the same *t*. Again we look at p[*s*][*t*] and continue the same till we reach *t,* all along printing *r (=p[ s][t])*.

### Incremental Node Addition

Suppose as of now, we have 4 nodes and APSP is computed. At this point 5^{th} node arrives, along with some edges connecting the existing nodes. Instead of computing APSP from the scratch, at a cost of *O(|V| ^{3}) = O(125)*, we can use the already computed APSP and extend that to complete it for 5 nodes, at a cost of

*O(|V|*.

^{2}) = O(25)### Adjusting Edge Weight Changes

What if weight for an edge changes (increases or decreases)? Do we need to re-compute APSP from scratch? Or we can adjust the existing results using some partial computations?