Floyd-Warshall Algorithm
The Floyd-Warshall algorithm solves the all-pairs shortest path problem — it finds the shortest path between every pair of vertices in a weighted graph in a single run. While Dijkstra's computes shortest paths from one source, Floyd-Warshall gives you the complete distance matrix for all V² pairs at once.
The Key Insight: Intermediate Vertices
The algorithm's brilliance lies in a simple question: can we improve the path from i to j by routing through some intermediate vertex k? It iterates over every possible intermediate vertex k and asks: is the path i → k → j shorter than the best known direct path i → j? If yes, update the distance.
Core recurrence (applied for each intermediate vertex k):
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
↑ ↑ ↑
current best best path best path
i → j i → k k → j
After processing all k from 0 to V-1, dist[i][j] holds
the true shortest path between every pair (i, j).Initialization Rules
Before the main triple loop starts, the distance matrix is initialized from the graph's edge weights:
dist[i][i] = 0 — the distance from any vertex to itself is zero
dist[i][j] = weight(i, j) — if a direct edge exists between i and j
dist[i][j] = Infinity — if there is no direct edge from i to j
Example graph (4 vertices: 0, 1, 2, 3):
5
(0)-------(1)
| \ |
1| 3\ |2
| \ |
(2)-------(3)
4
Edges: 0→1 (5), 0→2 (1), 0→3 (3), 1→3 (2), 2→3 (4)
Initial dist matrix:
0 1 2 3
0 [ 0, 5, 1, 3 ]
1 [ ∞, 0, ∞, 2 ]
2 [ ∞, ∞, 0, 4 ]
3 [ ∞, ∞, ∞, 0 ]Step-by-Step Walkthrough
Let's trace through the algorithm on the 4-vertex graph above. We run three nested loops: for k (intermediate), for i (source), for j (destination).
=== k = 0 (route through vertex 0) ===
Check all pairs i,j: can we do better via vertex 0?
i=1, j=2: dist[1][2] = min(∞, dist[1][0] + dist[0][2]) = min(∞, ∞+1) = ∞ (no change)
i=1, j=3: dist[1][3] = min(2, dist[1][0] + dist[0][3]) = min(2, ∞) = 2 (no change)
i=2, j=1: dist[2][1] = min(∞, dist[2][0] + dist[0][1]) = min(∞, ∞+5) = ∞ (no change)
i=2, j=3: dist[2][3] = min(4, dist[2][0] + dist[0][3]) = min(4, ∞) = 4 (no change)
Matrix after k=0: unchanged.
=== k = 1 (route through vertex 1) ===
i=0, j=3: dist[0][3] = min(3, dist[0][1] + dist[1][3]) = min(3, 5+2) = 3 (no change)
i=2, j=3: dist[2][3] = min(4, dist[2][1] + dist[1][3]) = min(4, ∞) = 4 (no change)
Matrix after k=1: unchanged.
=== k = 2 (route through vertex 2) ===
i=0, j=3: dist[0][3] = min(3, dist[0][2] + dist[2][3]) = min(3, 1+4) = 3 (no change)
Matrix after k=2: unchanged.
=== k = 3 (route through vertex 3) ===
(no outgoing edges from 3 in this graph)
Final dist matrix:
0 1 2 3
0 [ 0, 5, 1, 3 ]
1 [ ∞, 0, ∞, 2 ]
2 [ ∞, ∞, 0, 4 ]
3 [ ∞, ∞, ∞, 0 ]
Shortest path 0→3: direct edge weight 3.
Shortest path 0→1: direct edge weight 5 (no shorter route exists).
Shortest path 2→3: direct edge weight 4.Full JavaScript Implementation
/**
* Floyd-Warshall: all-pairs shortest paths
*
* @param {number} n - number of vertices (0-indexed: 0..n-1)
* @param {number[][]} edges - [from, to, weight]
* @returns {{ dist: number[][], next: (number|null)[][] }}
* dist[i][j] = shortest distance from i to j (Infinity if unreachable)
* next[i][j] = first step on the shortest path from i to j (for reconstruction)
*/
function floydWarshall(n, edges) {
const INF = Infinity;
// 1. Initialize distance matrix
const dist = Array.from({ length: n }, (_, i) =>
Array.from({ length: n }, (_, j) => (i === j ? 0 : INF))
);
// 2. Initialize "next" matrix for path reconstruction
// next[i][j] = the vertex after i on the shortest path to j
const next = Array.from({ length: n }, () => Array(n).fill(null));
// 3. Populate direct edges
for (const [u, v, w] of edges) {
if (w < dist[u][v]) { // handle parallel edges (keep smallest)
dist[u][v] = w;
next[u][v] = v;
}
}
// Self-loops: we go "nowhere" on a zero-length path
for (let i = 0; i < n; i++) next[i][i] = i;
// 4. Main triple loop — the heart of Floyd-Warshall
for (let k = 0; k < n; k++) { // intermediate vertex
for (let i = 0; i < n; i++) { // source
for (let j = 0; j < n; j++) { // destination
if (dist[i][k] === INF || dist[k][j] === INF) continue; // avoid Inf + Inf
const through = dist[i][k] + dist[k][j];
if (through < dist[i][j]) {
dist[i][j] = through;
next[i][j] = next[i][k]; // go to k first, then on to j
}
}
}
}
return { dist, next };
}
// --- Path Reconstruction ---
function getPath(next, u, v) {
if (next[u][v] === null) return []; // unreachable
const path = [u];
while (u !== v) {
u = next[u][v];
path.push(u);
}
return path;
}
// --- Example usage ---
const n = 4;
const edges = [
[0, 1, 5],
[0, 2, 1],
[0, 3, 3],
[1, 3, 2],
[2, 3, 4],
];
const { dist, next } = floydWarshall(n, edges);
console.log("Shortest distances:");
for (let i = 0; i < n; i++) {
console.log(` From ${i}:`, dist[i].map(d => d === Infinity ? "∞" : d));
}
console.log("\nPath from 0 to 3:", getPath(next, 0, 3)); // [0, 3]
console.log("Path from 2 to 1:", getPath(next, 2, 1)); // [] (unreachable in directed graph)
Detecting Negative Cycles
A negative cycle is a cycle whose total edge weight is negative. If a negative cycle is reachable between i and j, the "shortest" path is −∞ (you can loop forever to decrease the cost infinitely). Floyd-Warshall detects this elegantly.
After running Floyd-Warshall: If dist[i][i] < 0 for any vertex i, then vertex i is part of a negative cycle. More precisely: if dist[i][i] < 0 after the algorithm, the "shortest path" from i back to itself is negative — meaning you can keep looping around that cycle to reduce any path that passes through i to -∞.
function hasNegativeCycle(dist, n) {
for (let i = 0; i < n; i++) {
if (dist[i][i] < 0) return true;
}
return false;
}
// Any path through a vertex v in a negative cycle is also -∞
function isDistInfinitelyNegative(dist, n, u, v) {
for (let k = 0; k < n; k++) {
// If k is in a negative cycle AND is reachable from u AND can reach v
if (dist[k][k] < 0 && dist[u][k] !== Infinity && dist[k][v] !== Infinity) {
return true;
}
}
return false;
}
// Graph with negative cycle: 0→1 (1), 1→2 (-3), 2→1 (1)
// Cycle 1→2→1 has total weight -3+1 = -2 (negative!)
const edgesWithNegCycle = [
[0, 1, 1],
[1, 2, -3],
[2, 1, 1], // forms cycle 1→2→1 with weight -3+1 = -2
];
const { dist: d2 } = floydWarshall(3, edgesWithNegCycle);
console.log("Negative cycle?", hasNegativeCycle(d2, 3)); // true
console.log("dist[1][1] =", d2[1][1]); // < 0
Transitive Closure
The transitive closure of a graph is a matrix reach[i][j] that is true if vertex j is reachable from vertex i (by any path, regardless of weight). Floyd-Warshall can compute this directly by replacing addition with logical OR and min with logical AND.
/**
* Compute transitive closure: reach[i][j] = true if j is reachable from i
*/
function transitiveClosure(n, edges) {
// Initialize: reach[i][j] = true if direct edge i→j exists, or i === j
const reach = Array.from({ length: n }, (_, i) =>
Array.from({ length: n }, (_, j) => i === j)
);
for (const [u, v] of edges) {
reach[u][v] = true;
}
// Floyd-Warshall variant: use OR instead of min
for (let k = 0; k < n; k++) {
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
reach[i][j] = reach[i][j] || (reach[i][k] && reach[k][j]);
}
}
}
return reach;
}
const edges = [[0, 1], [1, 2], [2, 3]];
const reach = transitiveClosure(4, edges);
console.log("0 can reach 3?", reach[0][3]); // true (0→1→2→3)
console.log("3 can reach 0?", reach[3][0]); // false (no back edges)
console.log("2 can reach 1?", reach[2][1]); // false
Complexity Analysis
Aspect | Value | Explanation |
|---|---|---|
Time complexity | O(V³) | Three nested loops, each 0..V-1 |
Space complexity | O(V²) | dist[][] and next[][] matrices, each V×V |
Handles negative weights | Yes | Unlike Dijkstra's algorithm |
Handles negative cycles | Detects only | Distances become incorrect; check dist[i][i] |
Works on disconnected graphs | Yes | Unreachable pairs stay at Infinity |
Works on undirected graphs | Yes | Initialize both directions of each edge |
Floyd-Warshall vs Other Shortest Path Algorithms
Algorithm | Source | Negative weights | Negative cycles | Time | Best for |
|---|---|---|---|---|---|
Dijkstra's | Single source | No | No | O((V+E) log V) | Sparse graphs, one source |
Bellman-Ford | Single source | Yes | Detects | O(VE) | Negative weights, one source |
Floyd-Warshall | All pairs | Yes | Detects | O(V³) | Dense graphs, all pairs |
Johnson's | All pairs | Yes | No | O(VE + V² log V) | Sparse graphs, all pairs |
Problem 1: Find the City With the Smallest Number of Neighbors (LeetCode 1334)
Given n cities and edges with distances, find the city that has the fewest number of cities reachable within a distance threshold. Break ties by returning the city with the greater index. This is a textbook Floyd-Warshall application: compute all-pairs shortest paths, then count reachable cities per source.
/**
* LeetCode 1334 — Find the City With the Smallest Number of Neighbors
* at a Threshold Distance
*
* @param {number} n
* @param {number[][]} edges - [from, to, weight]
* @param {number} distanceThreshold
* @return {number}
*/
var findTheCity = function(n, edges, distanceThreshold) {
const INF = Infinity;
// 1. Initialize distance matrix
const dist = Array.from({ length: n }, (_, i) =>
Array.from({ length: n }, (_, j) => (i === j ? 0 : INF))
);
// 2. Fill direct edges (undirected)
for (const [u, v, w] of edges) {
dist[u][v] = Math.min(dist[u][v], w);
dist[v][u] = Math.min(dist[v][u], w); // undirected
}
// 3. Floyd-Warshall
for (let k = 0; k < n; k++) {
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
if (dist[i][k] !== INF && dist[k][j] !== INF) {
dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]);
}
}
}
}
// 4. For each city, count how many other cities are within threshold
let bestCity = -1;
let bestCount = Infinity;
for (let i = 0; i < n; i++) {
let count = 0;
for (let j = 0; j < n; j++) {
if (i !== j && dist[i][j] <= distanceThreshold) {
count++;
}
}
// Keep city with fewest neighbors; on tie, prefer higher index
if (count <= bestCount) {
bestCount = count;
bestCity = i;
}
}
return bestCity;
};
// Example: n=4, edges=[[0,1,3],[1,2,1],[1,3,4],[2,3,1]], threshold=4
// Expected: 3
console.log(findTheCity(4, [[0,1,3],[1,2,1],[1,3,4],[2,3,1]], 4)); // 3
Problem 2: Network Delay Time — All-Pairs Version (LeetCode 743)
The original problem asks for the minimum time for a signal to reach all nodes from a single source k. Using Floyd-Warshall, we can answer this for every possible source in one shot, which is useful when the source may change at query time.
/**
* Network Delay Time — answered for ANY source using Floyd-Warshall
*
* @param {number[][]} times - [u, v, w]: directed edge u→v with delay w
* @param {number} n - number of nodes (1-indexed)
* @param {number} k - source node
* @return {number} - time for signal to reach all nodes, or -1
*/
var networkDelayTime = function(times, n, k) {
const INF = Infinity;
// Convert 1-indexed to 0-indexed
const dist = Array.from({ length: n }, (_, i) =>
Array.from({ length: n }, (_, j) => (i === j ? 0 : INF))
);
for (const [u, v, w] of times) {
dist[u - 1][v - 1] = Math.min(dist[u - 1][v - 1], w);
}
// Floyd-Warshall
for (let mid = 0; mid < n; mid++) {
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
if (dist[i][mid] !== INF && dist[mid][j] !== INF) {
dist[i][j] = Math.min(dist[i][j], dist[i][mid] + dist[mid][j]);
}
}
}
}
// Answer for source k (0-indexed: k-1)
const src = k - 1;
let maxDelay = 0;
for (let j = 0; j < n; j++) {
if (dist[src][j] === INF) return -1; // node j unreachable
maxDelay = Math.max(maxDelay, dist[src][j]);
}
return maxDelay;
// Bonus: now dist[i] holds delays from ANY source i — O(1) queries!
};
console.log(networkDelayTime([[2,1,1],[2,3,1],[3,4,1]], 4, 2)); // 2
console.log(networkDelayTime([[1,2,1]], 2, 1)); // 1
console.log(networkDelayTime([[1,2,1]], 2, 2)); // -1 (node 1 unreachable from 2)
Problem 3: Transitive Closure of a Graph
Given a directed graph, determine for each pair (i, j) whether j is reachable from i. This is useful for dependency resolution, privilege escalation checks, and reachability queries.
/**
* Transitive Closure — return a boolean matrix
* reach[i][j] = true means vertex j is reachable from vertex i
*/
function buildTransitiveClosure(n, edges) {
const reach = Array.from({ length: n }, (_, i) =>
Array.from({ length: n }, (_, j) => i === j)
);
for (const [u, v] of edges) {
reach[u][v] = true;
}
for (let k = 0; k < n; k++) {
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
if (reach[i][k] && reach[k][j]) {
reach[i][j] = true;
}
}
}
}
return reach;
}
// Example: 4 nodes, edges form 0→1→2→3 (chain)
const edges = [[0,1],[1,2],[2,3]];
const reach = buildTransitiveClosure(4, edges);
console.log("Reachability matrix:");
reach.forEach((row, i) =>
console.log(` ${i}:`, row.map(b => b ? "T" : "F").join(" "))
);
// 0: T T T T (0 can reach everything)
// 1: F T T T (1 can reach 1,2,3)
// 2: F F T T (2 can reach 2,3)
// 3: F F F T (3 can only reach itself)
Problem 4: Detecting a Negative Cycle
After running Floyd-Warshall, inspect the main diagonal: if dist[i][i] < 0 for any i,
a negative cycle exists in the graph. This is useful for detecting arbitrage opportunities
in currency exchange or impossibility in certain scheduling problems.
/**
* Return all vertices that lie on a negative cycle.
*/
function findNegativeCycleVertices(n, edges) {
const INF = Infinity;
const dist = Array.from({ length: n }, (_, i) =>
Array.from({ length: n }, (_, j) => (i === j ? 0 : INF))
);
for (const [u, v, w] of edges) {
dist[u][v] = Math.min(dist[u][v], w);
}
for (let k = 0; k < n; k++) {
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
if (dist[i][k] !== INF && dist[k][j] !== INF) {
dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]);
}
}
}
}
// Vertices with dist[v][v] < 0 are on a negative cycle
const onNegCycle = [];
for (let v = 0; v < n; v++) {
if (dist[v][v] < 0) onNegCycle.push(v);
}
return onNegCycle;
}
// Graph: 0→1 (2), 1→2 (-5), 2→0 (1)
// Cycle 0→1→2→0 has weight 2 + (-5) + 1 = -2 (negative!)
const edgesNeg = [[0,1,2],[1,2,-5],[2,0,1]];
console.log("Vertices on negative cycle:", findNegativeCycleVertices(3, edgesNeg));
// [0, 1, 2] — all three are part of the cycle
// Graph with no negative cycle
const edgesPos = [[0,1,3],[1,2,1],[2,0,5]];
console.log("Vertices on negative cycle:", findNegativeCycleVertices(3, edgesPos));
// [] — cycle weight = 3+1+5 = 9 (positive)
Common Pitfalls
Integer overflow: when using large finite values instead of Infinity, dist[i][k] + dist[k][j] can overflow — use Infinity or check both values before adding
Forgetting 0-indexing: many problems use 1-indexed vertices; subtract 1 when building the matrix
Undirected graphs: always set both dist[u][v] and dist[v][u] during initialization
Parallel edges: when multiple edges exist between the same pair, keep only the minimum weight
Trusting results when negative cycles exist: dist values are meaningless for vertices on or reachable through a negative cycle — always check dist[i][i] first
Practice Problems
LeetCode 1334 — Find the City With the Smallest Number of Neighbors at a Threshold Distance
LeetCode 743 — Network Delay Time
LeetCode 399 — Evaluate Division (Floyd-Warshall on a ratio graph)
LeetCode 787 — Cheapest Flights Within K Stops (variant — Bellman-Ford fits better, but Floyd-Warshall works for all-pairs version)
CSES — Shortest Routes II (classic all-pairs Floyd-Warshall)
CSES — High Score (longest path with negative cycle detection)
GeeksForGeeks — Transitive Closure of a Graph
LeetCode 1462 — Course Schedule IV (transitive closure variant)