Floyd-Warshall Algorithm
Key Points
- â˘FloydâWarshall computes the shortest distances between all pairs of vertices in O() time using dynamic programming.
- â˘It iteratively improves paths by allowing intermediate vertices up to k, using the recurrence dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]).
- â˘The algorithm works with negative edge weights and detects negative cycles by checking if dist[i][i] < 0 for any i.
- â˘You only need one nĂn matrix for in-place updates, giving O() space, and this is safe because k increases monotonically.
- â˘It generalizes to other problems like transitive closure (Warshallâs algorithm over the Boolean semiring).
- â˘You can reconstruct actual shortest paths with a next matrix that remembers the next hop on each shortest path.
- â˘For contest problems, use long long with a large INF and guard against overflow when adding distances.
- â˘You can also count the number of shortest paths by tracking counts alongside distances and combining counts when equal minima occur.
Prerequisites
- âGraphs and representations (adjacency matrix/list) â FloydâWarshall operates on directed weighted graphs and is most naturally implemented with an adjacency matrix.
- âDynamic Programming fundamentals â The algorithmâs recurrence and in-place layering over k are core DP ideas.
- âSingle-source shortest paths (Dijkstra, BellmanâFord) â Helps compare trade-offs and understand handling of negative edges and cycles.
- âAsymptotic complexity (Big-O) â To judge when O(n^3) time and O(n^2) space are appropriate.
- âInteger ranges and overflow handling â Prevent erroneous updates when adding large INF sentinels in 64-bit arithmetic.
- âBoolean algebra and logic â For understanding Warshallâs transitive closure as OR/AND over paths.
Detailed Explanation
Tap terms for definitions01Overview
FloydâWarshall is a classic dynamic programming algorithm for the all-pairs shortest path (APSP) problem on a weighted directed graph. Instead of solving from one source at a time (like Dijkstra or BellmanâFord), it improves distances for every pair (i, j) together, by gradually permitting more possible intermediate vertices. After k steps, the algorithm guarantees that the current distance from i to j is the shortest among paths whose internal vertices are drawn from {0, 1, ..., kâ1}. The key insight is that any shortest path either avoids vertex k or uses it exactly once at some point, so you can always try to improve via i â k â j. With three nested loops over k, i, and j, every ordered triple is considered, yielding O(n^3) time.
FloydâWarshall gracefully handles negative edge weights and can detect negative cycles: if the best distance from i to i becomes negative, there is a cycle of negative total weight reachable from i. Moreover, it generalizes to other closure problems; with Boolean operations instead of numeric min/+, you get Warshallâs algorithm for transitive closure (reachability). With careful bookkeeping, you can reconstruct actual paths and even count how many distinct shortest paths exist. The algorithm is simple to implement, space efficient (O(n^2)), and especially strong on dense graphs or when you need many sourceâtarget queries answered at once.
02Intuition & Analogies
Imagine a city map where you want to know the fastest route between every pair of neighborhoods. One approach is to pick one neighborhood as a hub and compute distances to all others, repeating for every starting neighborhoodâthis is like running Dijkstra or BellmanâFord n times. FloydâWarshall takes a different perspective: line up all neighborhoods in a list, and say, âFirst, consider routes that are not allowed to pass through any neighborhood as an intermediate stop; next, allow passing through the first one; then allow the first two; and so on.â Each time you expand the set of allowed stopovers, some trips may become faster because a newly-allowed neighborhood serves as a helpful transfer point.
Think of building itineraries with more authorized layovers. For a fixed transfer node k, ask: could going from i to k and then from k to j beat my current best i â j plan? If yes, update it. If not, keep the old one. Repeat for every i, j. By the time youâve allowed all neighborhoods, youâve considered every possible place to transfer, so your plan between i and j must be optimal.
This also explains how negative cycles show up: if there exists a loop that reduces your total travel time whenever you circle it, the best âtime from a place back to itselfâ becomes negativeâthe sure sign that looping makes things arbitrarily better. Replace âtimeâ with âcan reach?â and swap min/+ with OR/AND and you get transitive closure: a path exists from i to j if either you already had one or you can go i â k and k â j. Same three-loop structure, different arithmetic.
03Formal Definition
04When to Use
Use FloydâWarshall when you need distances between many or all pairs of vertices and the graph is small-to-medium (e.g., n up to a few hundred) or fairly dense. It shines in problems where multiple queries of shortest paths are required after a one-time preprocessing step, because the O(n^3) preprocessing gives O(1) query time thereafter. It is also the go-to method when negative edge weights are present (but no negative cycles) and you need all-pairs results without the complexity of Johnsonâs algorithm.
Choose it for tasks like: computing minimal travel times between all cities; evaluating best exchange rates across many currencies (detecting arbitrage as a negative cycle); finding reachability/transitive closure in prerequisite graphs; or as a subroutine in dynamic programming on graphs where min-plus closure is natural. In contests, itâs a solid pick for n ⤠400â600 depending on time limits and constant factors, or whenever input is given as an adjacency matrix.
If the graph is very sparse and large, prefer Johnsonâs algorithm (reweights + Dijkstra) or run Dijkstra from all sources with a good priority queue. For only a few sources, simply run single-source algorithms from those sources.
â ď¸Common Mistakes
⢠Overflow with INF: Using a too-large INF and then adding dist[i][k] + dist[k][j] can overflow long long. Always guard by checking reachability first (dist[i][k] == INF or dist[k][j] == INF) before addition, or use INF = 4e18 and avoid adding when either term is INF.
⢠Not initializing the diagonal: Forgetting dist[i][i] = 0 breaks correctness and negative-cycle detection.
⢠Mishandling multiple edges: When reading edges, keep the minimum weight among parallel edges; otherwise, you may overestimate distances or miscount shortest paths.
⢠Wrong loop order or in-place doubts: The correct order is for k in 0..n-1, then i, then j. In-place updates are safe only with this order because you never use a value that still allows intermediates beyond k.
⢠Ignoring negative cycles: Simply reading dist after the algorithm without checking dist[i][i] < 0 can yield misleading finite numbers for pairs affected by negative cycles. Properly propagate ââ to all pairs (i, j) that can reach and exit a negatively-cycling vertex.
⢠Path reconstruction bugs: For the next-matrix approach, set next[i][j] = j on direct edges and update next[i][j] = next[i][k] upon relaxation. Donât try to reconstruct paths when iâj is unreachable or when a negative cycle affects the pair.
⢠Modulo and counting pitfalls: When counting shortest paths, ensure you reset counts on strictly better distances and only sum counts when new distance equals the existing optimum. Watch for negative cycles or zero-weight cycles which can lead to infinite counts.
Key Formulas
Base Initialization
Explanation: Start with zero distance to self, edge weights for direct edges, and infinity for non-edges. This sets up the DP table for improvements.
FloydâWarshall Recurrence
Explanation: Allowing vertex k as an intermediate can only improve if going through k is better than the current best. Repeat for all k to account for every possible intermediate.
Asymptotic Complexity
Explanation: Three nested loops over n vertices give cubic time, and the nĂn distance matrix uses quadratic space.
Negative Cycle Test
Explanation: If the best distance from a node to itself is negative after completion, there is a cycle of negative total weight, implying unboundedly decreasing path costs.
Semiring View
Explanation: The update can be expressed with min as addition and + as multiplication, highlighting that the algorithm is a closure computation in the min-plus semiring.
Warshall (Transitive Closure)
Explanation: Replace arithmetic with logical operations to compute reachability: a path exists if it already did or if i can reach k and k can reach j.
Counting Shortest Paths
Explanation: Maintain counts C alongside distances D. When a strictly better route via k is found, reset the count to the product of subcounts; when an equal route is found, add the product.
Complexity Analysis
Code Examples
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 static const long long INF = (long long)4e18; // safe INF for long long 5 6 // Reconstruct shortest path from s to t using next-hop matrix. 7 vector<int> reconstruct_path(int s, int t, const vector<vector<int>>& nxt) { 8 if (s == t) return {s}; 9 if (nxt[s][t] == -1) return {}; // unreachable 10 vector<int> path = {s}; 11 int u = s; 12 while (u != t) { 13 u = nxt[u][t]; 14 if (u == -1) { // defensive: should not happen if reachable 15 path.clear(); 16 return path; 17 } 18 path.push_back(u); 19 if ((int)path.size() > 1000000) break; // guard in pathological cases 20 } 21 return path; 22 } 23 24 int main() { 25 ios::sync_with_stdio(false); 26 cin.tie(nullptr); 27 28 int n, m; 29 // Example input format: 30 // n m 31 // m lines: u v w (0-indexed) 32 if (!(cin >> n >> m)) { 33 // Fallback demo graph if no input provided 34 n = 4; m = 5; 35 vector<tuple<int,int,long long>> edges = { 36 {0,1,5}, {0,3,10}, {1,2,3}, {2,3,1}, {0,2,100} 37 }; 38 vector<vector<long long>> dist(n, vector<long long>(n, INF)); 39 vector<vector<int>> nxt(n, vector<int>(n, -1)); 40 for (int i = 0; i < n; ++i) dist[i][i] = 0; 41 for (auto [u,v,w] : edges) { 42 if (w < dist[u][v]) { 43 dist[u][v] = w; 44 nxt[u][v] = v; 45 } 46 } 47 for (int k = 0; k < n; ++k) 48 for (int i = 0; i < n; ++i) 49 if (dist[i][k] != INF) 50 for (int j = 0; j < n; ++j) { 51 if (dist[k][j] == INF) continue; 52 long long cand = dist[i][k] + dist[k][j]; 53 if (cand < dist[i][j]) { 54 dist[i][j] = cand; 55 nxt[i][j] = nxt[i][k]; 56 } 57 } 58 // Print distances 59 cout << "Distance matrix:\n"; 60 for (int i = 0; i < n; ++i) { 61 for (int j = 0; j < n; ++j) { 62 if (dist[i][j] >= INF/2) cout << "INF "; else cout << dist[i][j] << ' '; 63 } 64 cout << '\n'; 65 } 66 // Reconstruct a sample path 0 -> 3 67 auto path = reconstruct_path(0, 3, nxt); 68 cout << "Path 0->3: "; 69 if (path.empty()) cout << "unreachable\n"; 70 else { 71 for (int i = 0; i < (int)path.size(); ++i) { 72 if (i) cout << " -> "; 73 cout << path[i]; 74 } 75 cout << '\n'; 76 } 77 return 0; 78 } 79 80 vector<vector<long long>> dist(n, vector<long long>(n, INF)); 81 vector<vector<int>> nxt(n, vector<int>(n, -1)); 82 for (int i = 0; i < n; ++i) dist[i][i] = 0; 83 84 for (int e = 0; e < m; ++e) { 85 int u, v; long long w; cin >> u >> v >> w; 86 if (w < dist[u][v]) { // keep the minimum among parallel edges 87 dist[u][v] = w; 88 nxt[u][v] = v; 89 } 90 } 91 92 for (int k = 0; k < n; ++k) 93 for (int i = 0; i < n; ++i) 94 if (dist[i][k] != INF) 95 for (int j = 0; j < n; ++j) { 96 if (dist[k][j] == INF) continue; 97 long long cand = dist[i][k] + dist[k][j]; 98 if (cand < dist[i][j]) { 99 dist[i][j] = cand; 100 nxt[i][j] = nxt[i][k]; 101 } 102 } 103 104 // Output distances 105 for (int i = 0; i < n; ++i) { 106 for (int j = 0; j < n; ++j) { 107 if (dist[i][j] >= INF/2) cout << "INF"; 108 else cout << dist[i][j]; 109 cout << (j+1==n?'\n':' '); 110 } 111 } 112 113 // Example query: reconstruct path from 0 to n-1 if desired 114 // vector<int> p = reconstruct_path(0, n-1, nxt); 115 return 0; 116 } 117
This implementation initializes the distance matrix with 0 on the diagonal, direct edge weights elsewhere, and INF when no edge exists. It updates distances in-place while k grows, safely using the next matrix to reconstruct actual shortest paths by walking from s to t via recorded next hops.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 static const long long INF = (long long)4e18; 5 6 int main() { 7 ios::sync_with_stdio(false); 8 cin.tie(nullptr); 9 10 int n, m; cin >> n >> m; 11 vector<vector<long long>> dist(n, vector<long long>(n, INF)); 12 for (int i = 0; i < n; ++i) dist[i][i] = 0; 13 14 for (int e = 0; e < m; ++e) { 15 int u, v; long long w; cin >> u >> v >> w; 16 dist[u][v] = min(dist[u][v], w); 17 } 18 19 for (int k = 0; k < n; ++k) 20 for (int i = 0; i < n; ++i) 21 if (dist[i][k] != INF) 22 for (int j = 0; j < n; ++j) { 23 if (dist[k][j] == INF) continue; 24 long long cand = dist[i][k] + dist[k][j]; 25 if (cand < dist[i][j]) dist[i][j] = cand; 26 } 27 28 // Mark pairs affected by any negative cycle with -INF 29 vector<vector<bool>> neg(n, vector<bool>(n, false)); 30 for (int v = 0; v < n; ++v) if (dist[v][v] < 0) { 31 for (int i = 0; i < n; ++i) if (dist[i][v] != INF) 32 for (int j = 0; j < n; ++j) if (dist[v][j] != INF) 33 neg[i][j] = true; // any path i->...->v->...->j can be made arbitrarily small 34 } 35 36 // Print result matrix with INF and -INF annotations 37 for (int i = 0; i < n; ++i) { 38 for (int j = 0; j < n; ++j) { 39 if (neg[i][j]) cout << "-INF"; 40 else if (dist[i][j] >= INF/2) cout << "INF"; 41 else cout << dist[i][j]; 42 cout << (j+1==n?'\n':' '); 43 } 44 } 45 return 0; 46 } 47
After running FloydâWarshall, a negative cycle is detected if dist[v][v] < 0. Any pair (i, j) that can reach v and be reached from v is affected, because you can loop around the negative cycle to reduce the path cost without bound. The code marks such pairs as âINF and prints them accordingly.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 int main() { 5 ios::sync_with_stdio(false); 6 cin.tie(nullptr); 7 8 int n, m; cin >> n >> m; 9 vector<vector<unsigned char>> reach(n, vector<unsigned char>(n, 0)); 10 for (int i = 0; i < n; ++i) reach[i][i] = 1; // each node reaches itself 11 12 for (int e = 0; e < m; ++e) { 13 int u, v; cin >> u >> v; 14 reach[u][v] = 1; 15 } 16 17 for (int k = 0; k < n; ++k) 18 for (int i = 0; i < n; ++i) 19 if (reach[i][k]) 20 for (int j = 0; j < n; ++j) 21 reach[i][j] = (reach[i][j] || (reach[i][k] && reach[k][j])); 22 23 // Print reachability matrix (1 if reachable, 0 otherwise) 24 for (int i = 0; i < n; ++i) { 25 for (int j = 0; j < n; ++j) { 26 cout << (reach[i][j] ? 1 : 0) << (j+1==n?'\n':' '); 27 } 28 } 29 return 0; 30 } 31
This is the Boolean variant of FloydâWarshall. It replaces arithmetic with logical OR/AND and computes whether j is reachable from i via any sequence of intermediates. It is useful for dependency graphs, prerequisite checks, and computing closure of relations.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 static const long long INF = (long long)4e18; 5 static const long long MOD = 1000000007LL; 6 7 int main() { 8 ios::sync_with_stdio(false); 9 cin.tie(nullptr); 10 11 int n, m; cin >> n >> m; 12 vector<vector<long long>> dist(n, vector<long long>(n, INF)); 13 vector<vector<long long>> cnt(n, vector<long long>(n, 0)); 14 15 for (int i = 0; i < n; ++i) { 16 dist[i][i] = 0; 17 cnt[i][i] = 1; // one empty path from i to i (helps composition) 18 } 19 20 for (int e = 0; e < m; ++e) { 21 int u, v; long long w; cin >> u >> v >> w; 22 if (w < dist[u][v]) { 23 dist[u][v] = w; 24 cnt[u][v] = 1; // best direct edge so far 25 } else if (w == dist[u][v]) { 26 cnt[u][v] = (cnt[u][v] + 1) % MOD; // another edge with same minimal weight 27 } 28 } 29 30 for (int k = 0; k < n; ++k) 31 for (int i = 0; i < n; ++i) if (dist[i][k] != INF) 32 for (int j = 0; j < n; ++j) { 33 if (dist[k][j] == INF) continue; 34 long long cand = dist[i][k] + dist[k][j]; 35 if (cand < dist[i][j]) { 36 dist[i][j] = cand; 37 cnt[i][j] = (cnt[i][k] * cnt[k][j]) % MOD; // reset with new best 38 } else if (cand == dist[i][j]) { 39 cnt[i][j] = (cnt[i][j] + (cnt[i][k] * cnt[k][j]) % MOD) % MOD; // add equal-best decompositions 40 } 41 } 42 43 // NOTE: This assumes no negative cycles affect any pair; otherwise counts may be infinite. 44 45 // Output: for each pair print distance and number of shortest paths 46 for (int i = 0; i < n; ++i) { 47 for (int j = 0; j < n; ++j) { 48 if (dist[i][j] >= INF/2) { 49 cout << "INF:0"; // no path 50 } else { 51 // For i==j, distance 0 and cnt>=1 include the empty path. 52 cout << dist[i][j] << ':' << (cnt[i][j] % MOD); 53 } 54 cout << (j+1==n?'\n':' '); 55 } 56 } 57 58 return 0; 59 } 60
The code maintains both distances and counts. When a strictly better path via k is found, it replaces the count with the product of subcounts (number of ways to reach k times number of ways from k to j). When an equal path is found, it adds the product. This follows the same DP layering by k and avoids double counting. We print distances and the number of shortest paths modulo 1e9+7.