From Distances to Routes
Floyd–Warshall computes the shortest distance between every ordered pair of vertices, but a distance alone does not say which edges form a route attaining it. To recover routes, the algorithm can maintain a small amount of additional information whenever it improves a distance. This tutorial develops next-hop and predecessor tables, explains how to use them, and makes explicit what to do when a pair is unreachable.
Throughout, \(G=(V,E)\) is a finite directed graph with no loops or multiple edges. Each edge \(uv\) has a real weight \(w(u,v)\); weights may be negative. Assume that \(G\) has no negative-weight cycle. A path is a sequence of distinct vertices with a directed edge between each consecutive pair; its weight is the sum of those edge weights. The shortest-path distance \(D[i,j]\) is the minimum weight of a path from \(i\) to \(j\), and is \(\infty\) if there is no such path. The empty path from a vertex to itself has weight zero, so \(D[i,i]=0\).
The no-negative-cycle assumption ensures that every reachable pair has a finite minimum: if a route repeats a vertex, the portion between repetitions is a cycle of nonnegative weight, which can be removed without increasing the route's weight. Repeating this removal gives a path, and there are only finitely many paths in a finite graph. If a negative cycle is present, distances for pairs that can reach and leave that cycle need separate treatment; the route-reconstruction rules below assume no such cycle.
Adding Route Data to Floyd–Warshall
Use the usual Floyd–Warshall vertex order \(1,\ldots,n\). Initialize \(D[i,i]=0\), and set \(D[i,j]=w(i,j)\) for each directed edge \(ij\); set \(D[i,j]=\infty\) when there is no edge and \(i\ne j\). Set \(N[i,j]=j\) for a direct edge \(ij\), and NIL for all other entries. The corresponding direct-edge predecessor is \(P[i,j]=i\); other predecessor entries are NIL.
At the stage that allows vertex \(k\) as an intermediate vertex, compare the current distance with the route through \(k\). If that route strictly improves the distance, update the distance and route data together:
The next hop is inherited from the first part, \(i\) to \(k\); the predecessor of \(j\) is inherited from the second part, \(k\) to \(j\). If the two distances are equal, keep the existing route data. This strict-improvement rule avoids replacing a route merely because another route ties it. In particular, it does not add unnecessary zero-weight cycles to an already optimal route. Under the no-negative-cycle assumption, an improving candidate cannot require a missing next hop or predecessor: a candidate using a diagonal empty segment cannot strictly improve a distance.
Proof. Initially, each finite off-diagonal entry is a direct edge, and its next hop and predecessor are exactly its endpoint data. Each diagonal entry describes the empty path and has no route pointer. Suppose the invariant holds before processing \(k\). A route allowed after this stage either does not use \(k\) as an intermediate vertex, or can be split at \(k\) into a route from \(i\) to \(k\) and one from \(k\) to \(j\). The best candidate of the second kind has weight \(D[i,k]+D[k,j]\), so comparing it with the old \(D[i,j]\) gives the Floyd–Warshall update. If it is strictly better, its first edge is the first edge of the selected \(i\)-to-\(k\) route, and its last edge is the last edge of the selected \(k\)-to-\(j\) route. Thus the stated next-hop and predecessor updates describe the improved route. If it does not improve, retaining the old route data preserves the invariant. This proves the claim at every stage by induction. In the final table, a selected shortest route has shortest subroutes: replacing a subroute by a strictly better one would produce a route of smaller total weight. Therefore the next-hop pointer at each vertex leads along a selected shortest route to the destination; the predecessor pointer gives the same route in reverse. Since a shortest route can be chosen to be a path, reconstruction reaches its destination without repeating vertices. \(\square\)
For equal-weight alternatives, the rule above selects whichever route was recorded first. If a particular tie-breaking policy is wanted, apply it consistently, for example by storing the number of edges and preferring fewer edges when distances tie. The selected route may differ, but its distance remains the shortest distance.
Reconstructing and Reporting Routes
For a reachable pair \(s\ne t\), start at \(s\), append \(N[s,t]\), then look up the next hop for the new current vertex and the same destination \(t\). Continue until reaching \(t\). For \(s=t\), return the single-vertex route \(s\). If \(D[s,t]=\infty\), report that no directed route exists; do not try to follow pointers. With predecessor data, start at \(t\), repeatedly replace the current vertex \(v\) by \(P[s,v]\), and reverse the resulting list at the end.
Proof. If a directed path exists, it has finitely many edges and hence finite weight. Its minimum-weight path therefore has finite distance, so \(D[s,t]\ne\infty\). Conversely, a finite distance can only be initialized from an edge or obtained by concatenating two finite distances. Inductively, each finite entry represents a route. Thus a finite final entry implies a path exists. If \(s=t\), the empty path gives distance zero, so the diagonal is not an unreachable pair. \(\square\)
Worked Example: Recovering a Route Through Several Updates
Let \(G\) have vertex set \(\{1,2,3,4,5\}\) and directed weighted edges \(12\) of weight \(4\), \(13\) of weight \(10\), \(23\) of weight \(2\), \(24\) of weight \(7\), \(34\) of weight \(1\), and \(45\) of weight \(3\). All unlisted directed edges are absent. We reconstruct a shortest route from \(1\) to \(5\).
Initially, \(D[1,2]=4\), \(D[1,3]=10\), \(D[2,3]=2\), \(D[2,4]=7\), \(D[3,4]=1\), and \(D[4,5]=3\). The corresponding next hops are respectively \(2,3,3,4,4,5\). All other off-diagonal distances begin at \(\infty\).
When \(k=2\), the route \(1\to2\to3\) has weight \(4+2=6\), which is less than the current \(D[1,3]=10\). Update \(D[1,3]=6\) and \(N[1,3]=N[1,2]=2\). The route \(1\to2\to4\) has weight \(4+7=11\), which does not improve \(D[1,4]=\infty\); it sets \(D[1,4]=11\) and \(N[1,4]=2\). At \(k=3\), \(2\to3\to4\) has weight \(2+1=3<7\), so \(D[2,4]=3\) and \(N[2,4]=3\). Also \(1\to3\to4\) has weight \(6+1=7<11\), so \(D[1,4]=7\) and \(N[1,4]=N[1,3]=2\). At \(k=4\), \(1\to4\to5\) has weight \(7+3=10\), improving \(D[1,5]=\infty\); hence \(D[1,5]=10\) and \(N[1,5]=N[1,4]=2\).
The final distances from vertex \(1\) to vertices \(2,3,4,5\) are \(4,6,7,10\), respectively. The next-hop entries for these destinations are all \(2\). To reconstruct \(1\) to \(5\), start at \(1\): \(N[1,5]=2\), so take \(1\to2\). Then \(N[2,5]=3\), so take \(2\to3\); \(N[3,5]=4\), so take \(3\to4\); and \(N[4,5]=5\), so take \(4\to5\).
Check: the route is \(1\to2\to3\to4\to5\). Its weight is \(4+2+1+3=10\), the table's distance \(D[1,5]\). The other route from \(1\) to \(5\) using the direct edge \(13\) has weight \(10+1+3=14\), and the route \(1\to2\to4\to5\) has weight \(4+7+3=14\), so neither improves the reported route.
Worked Example: Reconstructing with Predecessors
Let \(G\) have vertex set \(\{s,a,b,t\}\) and directed weighted edges \(sa\) of weight \(4\), \(sb\) of weight \(5\), \(ab\) of weight \(-2\), \(bt\) of weight \(3\), and \(at\) of weight \(6\). There are no other edges. There is no cycle, so in particular there is no negative cycle. Find the shortest route from \(s\) to \(t\) using predecessor data.
The route \(s\to a\to b\) has weight \(4+(-2)=2\), improving the direct distance \(D[s,b]=5\). Its predecessor for \(b\) is \(a\), so \(P[s,b]=a\). Then the route \(s\to a\to b\to t\) has weight \(2+3=5\), improving the route \(s\to a\to t\), whose weight is \(4+6=10\). The predecessor of \(t\) on the improved route is \(P[b,t]=b\), so \(P[s,t]=b\). The shortest distance is therefore \(D[s,t]=5\).
Reconstruct backwards: start at \(t\); \(P[s,t]=b\), so move to \(b\); \(P[s,b]=a\), so move to \(a\); \(P[s,a]=s\), so move to \(s\). Reverse the list \(t,b,a,s\) to obtain \(s\to a\to b\to t\).
Check: its weight is \(4+(-2)+3=5\). The other route to \(t\), \(s\to a\to t\), has weight \(4+6=10\), while \(s\to b\to t\) has weight \(5+3=8\). The reconstructed route has the least of these route weights.
Worked Example: Reporting an Unreachable Pair
Let \(G\) have vertex set \(\{a,b,c,d\}\) and directed weighted edges \(ab\) of weight \(2\), \(bc\) of weight \(1\), and \(da\) of weight \(5\). There are no other edges. Determine whether there is a route from \(c\) to \(a\), and find the distance from \(d\) to \(c\).
Vertex \(c\) has no outgoing edge. Therefore there is no directed path from \(c\) to \(a\), \(D[c,a]=\infty\), and \(N[c,a]\) and \(P[c,a]\) remain NIL. The correct output for this pair is “unreachable”; following a NIL entry would not produce a route. From \(d\), the edges \(da\), \(ab\), and \(bc\) give the route \(d\to a\to b\to c\). Its weight is \(5+2+1=8\), so \(D[d,c]=8\).
Check: the only outgoing edge from \(d\) is \(da\), and from \(a\) the only outgoing edge is \(ab\), followed by \(bc\). Thus the displayed route is the only directed route from \(d\) to \(c\), and its weight \(8\) is necessarily shortest. Since \(c\) has no outgoing edge, the unreachable conclusion for \(c\) to \(a\) is also verified directly.
Common Mistakes
A frequent error is to update \(D[i,j]\) but forget to update its route pointer. That leaves a distance from the improved route and a pointer from the old route, so following the table may return a route whose weight does not equal \(D[i,j]\). In the first example, after improving \(D[1,3]\) from \(10\) to \(6\) through vertex \(2\), retaining \(N[1,3]=3\) would incorrectly claim that the first step on the selected route is \(1\to3\), whose weight is \(10\). The correction is to update \(N[1,3]\) to \(N[1,2]=2\) at the same time as the distance.
Another mistake is to treat \(\infty\) as an ordinary number and attempt route reconstruction anyway. An unreachable pair has no first edge to follow. Check whether \(D[s,t]\) is finite before consulting the route table. Also keep the direction of predecessor reconstruction straight: predecessors are followed from the destination back to the source, and the resulting vertex list must be reversed.
Route tables matter whenever the desired output is an actual itinerary, communication route, or sequence of transitions rather than just its cost. Maintaining the pointer during the distance computation avoids searching the graph again for each requested pair. The essential consistency check is that the reconstructed route uses directed edges and that the sum of their weights equals the table entry.
Check Your Understanding
For each question, distinguish a finite shortest distance from the route information used to realize it.
- For a direct edge \(uv\), what are the initial values of \(N[u,v]\) and \(P[u,v]\)?
- Suppose a route through \(k\) strictly improves \(D[i,j]\). Explain why the new next hop comes from the \(i\)-to-\(k\) part and the new predecessor comes from the \(k\)-to-\(j\) part.
- In the first worked example, reconstruct the route from \(2\) to \(5\) and compute its weight.
- What should an implementation return when \(D[s,t]=\infty\), and why should it not follow a pointer?
- Why can equal-distance alternatives be left unchanged without affecting the correctness of the shortest distance?