← All Posts
DSA Series · Graphs · Floyd-Warshall Algorithm

Floyd-Warshall Algorithm

The Floyd-Warshall algorithm computes the shortest paths between every pair of vertices in a weighted graph — a problem known as All-Pairs Shortest Paths (APSP). It is one of the most elegant applications of dynamic programming in graph theory: just three nested loops and a single relaxation step. In this post we dissect the DP recurrence, walk through a full 5-node example showing the distance matrix evolve at each iteration, build path reconstruction with a next[][] array, explore negative-cycle detection and transitive closure, and provide an interactive animation you can step through at your own pace.

Why Floyd-Warshall?

Single-source algorithms like Dijkstra and Bellman-Ford answer “what is the shortest path from one source to all other vertices?” But many problems require shortest paths between every pair:

When to use Floyd-Warshall: The graph is small (V ≤ 500), you need all-pairs distances, or the graph has negative edge weights (but no negative cycles). For single-source queries on large sparse graphs, prefer Dijkstra or Bellman-Ford instead.

The DP Recurrence

Let dist(k)[i][j] denote the shortest path from i to j using only vertices {0, 1, …, k} as intermediate nodes. The base case is direct edge weights:

// Base case: direct edges only (no intermediates)
dist(−1)[i][j] = weight(i,j) if edge exists, otherwise
dist(−1)[i][i] = 0

// Recurrence: can we improve i→j by routing through vertex k?
dist(k)[i][j] = min(
dist(k−1)[i][j], // best path NOT using k as intermediate
dist(k−1)[i][k] + dist(k−1)[k][j] // path through k
)

After processing all k from 0 to V−1, dist(V−1)[i][j] holds the true shortest path from i to j (considering all possible intermediates).

Why k Must Be the Outer Loop

The recurrence builds on dist(k−1) values. If k were an inner loop, we would attempt to use intermediate vertex k before computing all (k−1)-optimal paths. The outer k loop ensures that when we process intermediate vertex k, all paths using intermediates {0, …, k−1} are already finalised. This ordering is what makes the DP correct.

In practice, we update the matrix in-place because the values dist[i][k] and dist[k][j] are never worsened by the relaxation step for intermediate k (a shortest path from i to k doesn’t benefit from routing through k again).

for k = 0 to V-1:
for i = 0 to V-1:
for j = 0 to V-1:
  dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

Step-by-Step Walkthrough

Let’s trace Floyd-Warshall on this 5-node directed, weighted graph. We will watch the distance matrix evolve as k goes from 0 to 4.

4 8 2 5 1 3 6 0 1 2 3 4

Edges: 0→1 (4), 0→4 (8), 1→2 (2), 1→3 (5), 2→3 (1), 3→4 (3), 4→2 (6).

Initial Distance Matrix (direct edges only)

Set dist[i][j] = weight(i,j) if edge exists, otherwise, and dist[i][i] = 0.

01234
0048
1025
201
303
460

Intermediate vertex 0

For each pair (i,j), check if dist[i][0] + dist[0][j] < dist[i][j]. Since no vertex has a finite-weight edge to vertex 0, all dist[i][0] = ∞ for i ≠ 0. No path through vertex 0 can improve anything.

No updates

Intermediate vertex 1

Vertex 0 can reach vertex 1 (dist[0][1]=4), and vertex 1 can reach vertices 2 and 3. So we discover new paths:

  • dist[0][2] = min(∞, 4+2) = 6   path 0→1→2
  • dist[0][3] = min(∞, 4+5) = 9   path 0→1→3
01234
004698
1025
201
303
460

Intermediate vertex 2

Vertex 2 can reach vertex 3 (dist[2][3]=1). Any vertex that can reach 2 may now improve its distance to 3:

  • dist[0][3] = min(9, 6+1) = 7   path 0→1→2→3
  • dist[1][3] = min(5, 2+1) = 3   path 1→2→3
  • dist[4][3] = min(∞, 6+1) = 7   path 4→2→3
01234
004678
1023
201
303
4670

Intermediate vertex 3

Vertex 3 can reach vertex 4 (dist[3][4]=3). Vertices that can now reach 3 may improve their distance to 4:

  • dist[1][4] = min(∞, 3+3) = 6   path 1→2→3→4
  • dist[2][4] = min(∞, 1+3) = 4   path 2→3→4
01234
004678
10236
2014
303
4670

Intermediate vertex 4

Vertex 4 can reach vertex 2 (dist[4][2]=6). Vertex 3 can reach 4 (dist[3][4]=3), so it discovers a path to 2:

  • dist[3][2] = min(∞, 3+6) = 9   path 3→4→2
01234
004678
10236
2014
3903
4670
✅ All-pairs shortest paths computed!

Notice how each k iteration can use improvements discovered in earlier k iterations. For example, at k=2 we used dist[0][2]=6, which was itself computed at k=1. This cascading improvement is the power of Floyd-Warshall’s DP.

Path Reconstruction

The distance matrix tells us the cost of shortest paths, but not the actual paths. To reconstruct paths, maintain a next[i][j] matrix: next[i][j] stores the first vertex after i on the shortest path from i to j.

// Initialisation
if edge(i,j) exists: next[i][j] = j
else: next[i][j] = −1

// During relaxation: when a shorter path through k is found
if dist[i][k] + dist[k][j] < dist[i][j]:
dist[i][j] = dist[i][k] + dist[k][j]
next[i][j] = next[i][k]  // route through k, so first hop is same as i→k

Reconstructing a Path

To print the shortest path from u to v, follow the next pointers:

function getPath(u, v, next):
if next[u][v] == −1: return “no path”
path = [u]
while u ≠ v:
u = next[u][v]
path.append(u)
return path

Example: Shortest path from 1 to 4 (cost 6). Starting at next[1][4] = 2, then next[2][4] = 3, then next[3][4] = 4. Path: 1 → 2 → 3 → 4 with total weight 2+1+3 = 6. ✔

Interactive Animation

Step through the Floyd-Warshall algorithm on our 5-node graph. Watch the distance matrix update as each intermediate vertex k is processed. Orange cells show values that improved in the current step.

Use Step for manual control, Play for auto-advance, or Reset to start over.

4 8 2 5 1 3 6 0 1 2 3 4
Default Current k Processed
01234
0048
1025
201
303
460

Detecting Negative Cycles

Floyd-Warshall handles negative edge weights correctly as long as no negative-weight cycle exists. But how do we detect one?

Key insight: After running Floyd-Warshall, check the diagonal of the distance matrix. If dist[i][i] < 0 for any vertex i, then vertex i lies on a negative-weight cycle. A shortest “path” from i to itself should be 0, but a negative cycle lets us go around and arrive with a smaller total.
// After running Floyd-Warshall:
for i = 0 to V-1:
if dist[i][i] < 0:
// vertex i is on a negative cycle

If a negative cycle is detected, the distances in the matrix are unreliable for vertices that can reach or be reached by the cycle. In competitive programming, you may need a second pass to mark all pairs (i,j) where the path passes through a negative cycle as having distance −∞: if dist[i][k] + dist[k][j] < dist[i][j] and dist[k][k] < 0, set dist[i][j] = −∞.

Transitive Closure (Warshall’s Algorithm)

A beautiful special case: replace arithmetic with boolean logic. Instead of “shortest distance,” we ask “can vertex i reach vertex j?” This is the transitive closure of the graph, and the algorithm is called Warshall’s algorithm (1962, the predecessor of Floyd-Warshall).

// Initialisation
reach[i][j] = true if edge(i,j) exists or i == j

for k = 0 to V-1:
for i = 0 to V-1:
for j = 0 to V-1:
  reach[i][j] = reach[i][j] OR (reach[i][k] AND reach[k][j])

The logic is identical to Floyd-Warshall: “can i reach j either directly or by going through k?” This is O(V³) but can be optimised with bitset operations to O(V³/64).

LeetCode 1462 — Course Schedule IV is a direct application of transitive closure. Given prerequisite pairs, answer queries “is course A a prerequisite of course B?” Run Warshall’s algorithm, then answer each query in O(1).

C++ Implementation

1. Basic Floyd-Warshall

#include <bits/stdc++.h>
using namespace std;

const long long INF = 1e18;

void floydWarshall(vector<vector<long long>>& dist) {
    int V = dist.size();
    for (int k = 0; k < V; k++)
        for (int i = 0; i < V; i++)
            for (int j = 0; j < V; j++)
                if (dist[i][k] < INF && dist[k][j] < INF)
                    dist[i][j] = min(dist[i][j],
                                     dist[i][k] + dist[k][j]);
}

int main() {
    int V = 5;
    vector<vector<long long>> dist(V, vector<long long>(V, INF));
    for (int i = 0; i < V; i++) dist[i][i] = 0;

    // Edges: u, v, weight
    dist[0][1] = 4; dist[0][4] = 8;
    dist[1][2] = 2; dist[1][3] = 5;
    dist[2][3] = 1; dist[3][4] = 3;
    dist[4][2] = 6;

    floydWarshall(dist);

    for (int i = 0; i < V; i++) {
        for (int j = 0; j < V; j++)
            cout << (dist[i][j] >= INF ? -1 : dist[i][j]) << "\t";
        cout << "\n";
    }
    return 0;
}
Overflow guard: The check dist[i][k] < INF prevents adding two large values that could overflow. Always use long long with INF = 1e18 in competitive programming.

2. With Path Reconstruction

void floydWarshallPath(vector<vector<long long>>& dist,
                        vector<vector<int>>& nxt) {
    int V = dist.size();
    // Initialise next: nxt[i][j] = j if edge exists
    nxt.assign(V, vector<int>(V, -1));
    for (int i = 0; i < V; i++)
        for (int j = 0; j < V; j++)
            if (dist[i][j] < INF && i != j)
                nxt[i][j] = j;

    for (int k = 0; k < V; k++)
        for (int i = 0; i < V; i++)
            for (int j = 0; j < V; j++)
                if (dist[i][k] < INF && dist[k][j] < INF
                    && dist[i][k] + dist[k][j] < dist[i][j]) {
                    dist[i][j] = dist[i][k] + dist[k][j];
                    nxt[i][j] = nxt[i][k];
                }
}

vector<int> getPath(int u, int v, vector<vector<int>>& nxt) {
    if (nxt[u][v] == -1) return {};  // no path
    vector<int> path = {u};
    while (u != v) {
        u = nxt[u][v];
        path.push_back(u);
    }
    return path;
}

3. Transitive Closure (Bitset-Optimised)

// O(V^3 / 64) with bitset optimisation
const int MAXV = 500;
bitset<MAXV> reach[MAXV];

void transitiveClosure(int V, vector<pair<int,int>>& edges) {
    for (int i = 0; i < V; i++) {
        reach[i].reset();
        reach[i].set(i);  // i reaches itself
    }
    for (auto& [u, v] : edges)
        reach[u].set(v);

    for (int k = 0; k < V; k++)
        for (int i = 0; i < V; i++)
            if (reach[i].test(k))
                reach[i] |= reach[k];
    // reach[i].test(j) == true means i can reach j
}

The bitset trick replaces the inner j-loop with a single bitwise OR operation, giving a 64× speedup in practice.

Complexity Analysis

AspectComplexityNotes
TimeO(V³)Three nested loops over V vertices
SpaceO(V²)Distance matrix (in-place update)
Path reconstruction+O(V²)Additional next[][] matrix
Path queryO(V)Follow next pointers

Comparison with repeated Dijkstra: Running Dijkstra from every vertex costs O(V · (V+E) log V) with a binary heap. For dense graphs (E ≈ V²), this is O(V³ log V), worse than Floyd-Warshall. For sparse graphs (E ≈ V), repeated Dijkstra is O(V² log V), much better. Floyd-Warshall wins on simplicity and cache performance for small V.

Rule of thumb: Use Floyd-Warshall when V ≤ 400–500. Beyond that, prefer repeated Dijkstra (for non-negative weights) or Johnson’s algorithm (for graphs with negative edges).

Competitive Programming Patterns

1. Shortest Paths in Small Graphs

The most common pattern: V ≤ 400, all-pairs shortest paths needed. Build the adjacency matrix, run Floyd-Warshall, answer queries in O(1). Often disguised as “find the city with fewest reachable neighbours within distance threshold” (LeetCode 1334).

2. Minimax Paths

Find the path from i to j that minimises the maximum edge weight along the path. Replace the relaxation with:
dist[i][j] = min(dist[i][j], max(dist[i][k], dist[k][j])).

3. Widest (Bottleneck) Paths

Find the path that maximises the minimum edge weight (widest bottleneck). Replace with:
dist[i][j] = max(dist[i][j], min(dist[i][k], dist[k][j])).

4. Incremental Vertex Addition

In problems like Codeforces 295B — Greg and Graph, vertices are added one by one. After adding vertex k, run just the k-th iteration of Floyd-Warshall (the outer loop for that single k). This updates all-pairs distances incrementally in O(V²) per vertex addition.

5. Detecting Negative Arbitrage Cycles

Model currency exchange rates as edge weights (use −log(rate) to convert multiplication to addition). Run Floyd-Warshall and check if any dist[i][i] < 0, indicating an arbitrage opportunity.

Practice Problems

Test your understanding with these problems, ordered by difficulty:

Shortest Routes II (CSES)

Direct application: given a weighted graph with up to 500 nodes, answer Q shortest-path queries. Textbook Floyd-Warshall.

Find the City (LeetCode 1334)

Run Floyd-Warshall, then for each city count how many other cities are within the distance threshold. Return the city with the smallest count.

Course Schedule IV (LeetCode 1462)

Transitive closure problem: determine if course A is a prerequisite of course B. Use Warshall’s boolean algorithm.

Evaluate Division (LeetCode 399)

Model variable equations as a weighted graph (a/b = 2.0 means edge a→b with weight 2.0). Use Floyd-Warshall with multiplication instead of addition to answer queries.

Greg and Graph (Codeforces 295B)

Vertices are added in a given order. After each addition, print the sum of all-pairs shortest distances. Solve by adding vertices in reverse and running partial Floyd-Warshall iterations.

Edge Deletion (AtCoder ABC243 E)

Find the maximum number of edges you can delete without changing any shortest-path distance. Run Floyd-Warshall, then check each edge: if an alternative path of the same length exists, the edge is redundant.

Traveling Graph (Codeforces 21D)

Find the shortest closed walk that visits every edge at least once (Chinese Postman Problem). Requires Floyd-Warshall to compute all-pairs shortest paths, then bitmask DP on odd-degree vertices.

Summary

  1. Floyd-Warshall solves all-pairs shortest paths in O(V³) time and O(V²) space via DP. The k-loop must be outermost for correctness.
  2. The recurrence dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]) considers all possible intermediate vertices.
  3. Path reconstruction uses a next[i][j] matrix updated alongside dist. Follow next pointers to recover the actual path.
  4. Negative cycles are detected by checking the diagonal: dist[i][i] < 0 means vertex i is on a negative cycle.
  5. Warshall’s algorithm (boolean variant) computes transitive closure with the same structure, optimisable to O(V³/64) using bitsets.
  6. Use Floyd-Warshall when V ≤ 500 and you need all-pairs distances. For larger or sparser graphs, prefer repeated Dijkstra or Johnson’s algorithm.
  7. In competitive programming, the same framework supports minimax paths, widest paths, and incremental vertex addition.
Practice tip: Floyd-Warshall is one of the simplest algorithms to code under contest pressure — just three loops and one comparison. The key is recognising when the problem reduces to all-pairs shortest paths on a small graph. Any time you see V ≤ 500 and need pairwise distances, reach for Floyd-Warshall.