The
Floyd–Warshall algorithm is a classic application of
dynamic
programming. It finds the shortest path between all pairs of nodes in a
directed graph in
What is less well known is that the algorithm can easily be expressed using memoization, with the same asymptotic performance and the benefit that the correctness of the algorithm is, arguably, more obvious.
Let
Assume that the
(* sp(i, j, k) is the length of the shortest path from node i
* to node j, where other than nodes i and j, the path includes only nodes
* whose index is at least k.
*)
val sp: int * int * int -> int
To compute the best path distance sp(i, j, 0)
.
Eliding memoization, this function has a straightforward recursive definition that follows from its specification:
let rec sp(i, j, k) = if k >= n then d(i, j) else (* No other nodes can be on path *) min (sp(i, k, k+1) + sp(k, j, k+1)) (* Best path including node k *) (sp(i, j, k+1)) (* Best path excluding node k *)
As written, this code takes exponential time. However, in a graph with
Adding memoization to this code can be done in multiple ways.
In OCaml, we can define a function memoize
that generically memoizes
recursive functions, using a hash table:
let memoize f = let h = Hashtbl.create 11 in let rec memoized x = try Hashtbl.find h x with Not_found -> let y = f memoized x in Hashtbl.add h x y; y in memoized
Next, we update the function sp
to take an extra argument that will be its
memoized self:
let sp sp (i, j, k) = if k >= n then d(i, j) else min (sp(i, k, k+1) + sp(k, j, k+1)) (sp(i, j, k+1))
And we can apply memoize
to this function to obtain the efficient
version of the algorithm!
let memo_sp = memoize sp let shortest_path i j = memo_sp(i,j,0)Andrew Myers