Skip to main content
  1. /classes/
  2. Classes, Spring 2026/
  3. CS 3221 Spring 2026: Course Site/

Notes: 03-27 All-Pairs Shortest Path

·660 words·4 mins·

Last time: Single Source Shortest Path

  • One start vertex
  • Find shortest path to each other vertex
  • This gives us a tree of paths

Imagine we’re building a computer game and need fast AI pathfinding on a large fixed map.

Step 1 is to build a navigation graph, which simplifies the continuous or very detailed level geometry into a simpler graph of region connectivity.

But even then we potentially have a big graph, and having many AI characters constantly running Djikstra’s or A* to find paths is computationally expensive.

The map isn’t changing, so let’s just compute all the paths, all at once, upfront. We’ll have to re-run this computation every time the map changes, but that’s dev-only and reasonably uncommon.

But how do we compute all the paths, all at once, efficiently?

We can just SSSP from each source, but can we do better?

  • If we Djikstra’s from each source, that’s O(|V| * |E| log |V|) = O(|V|^3 log |V|).
  • There’s an optimization called Johnson’s algorithm that gets us to O(|V|^2 log V + |V||E|) which is fast on sparse graphs.

Now, the book spends half it’s time on negative edge weights again, let’s ignore that for simplicity.

Let’s go ahead and try to solve this with a recurrence / dynamic programming.

  • We’re going to build up a |V|x|V| table of each distance (u -> v).
  • The l parameter is iteration count, and lets us get a clear recurrence and stopping condition.
dist(u, v, l) = 
  0 if l == 0 and u == v
  min(
    dist(u,v,l-1),
    dist(u,x,l-1) + min w(x,v) for each edge u,v
  )

Intuitively, we:

  • Initialize dist[u, v] = +inf for all u, v (except dist[v,v] = 0)
  • Then, for l = 0 to V - 1:
  • For each u, v:
  • Find the edge (x, v) that minimizes dist[u, x] + w(x, v) to update dist[u, v].
  • That’s O(|V|^4), which isn’t better than Djikstra’s.
  • That makes sense, since this is just a bunch of Bellman Ford in a different order.

This algorithm doesn’t make the best possible use of the memo table. In each step we look up a path of length l-1 and add one more edge. We could instead meet in the middle.

dist(u, v, l) = 
  w(u, v) if l == 1
  dist(u,x,l/2) + min dist(x,v,l/2) for each known dist u,v otherwise

We only need to iterate l up to V/2, so this takes O(|V|^3 log |V|).

Code:

for i = 1 .. ceil(log |V|):
  for all u in V:
    for all v in V:
      for all x in V:
        if dist[u, v] > dist[u, x] + dist[x, v]:
          dist[u,v] = dist[u, x] + dist[x, v]

Floyd-Warshall:

Instead of splitting on path length, let’s arbitrarily number the vertices 1..|V| and add only paths that pass through vertices 1, 2, …, |V| at each step.

  • P(u, v) will denote the length of the true shortest path from u -> v.
  • P(u, v, i) will denote the length of the shortest path from u -> v that passes through vertex i as an intermediate vertex.

We can see:

  • P(u, v, 0) is w(u, v) or +inf since it passes through no intermediate vertices. All non-inf paths are length 1.
  • P(u, v, 1) adds some correct shortest paths that path through vertex 1. All non-inf paths are length <= 2.
  • P(u, v, 2) adds some more that pass through vertex 2 and maybe vertex 1.
  • By induction we can show:
    • All the paths this gets are shortest paths.
    • We get paths for all connected pairs.

Funny Matrix Multiplication

Logically what we’re doing here is building a distance matrix.

The algorithm to do that ends up looking a lot like squaring a matrix, except with min instead of + and + instead of *. Lots of work has gone into matrix multiplication, and some of it applies to this problem, but it doesn’t end up giving us an improvement in complexity class in general.

Overflow

More Floyd-Warshall Examples

Nat Tuck
Author
Nat Tuck