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] = +inffor all u, v (exceptdist[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 updatedist[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