24. Bellman-Ford & Floyd-Warshall
873 words
4 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# 24. Bellman-Ford & Floyd-Warshall > **What problem does this solve?** Dijkstra fails with negative edge weights.

24. Bellman-Ford & Floyd-Warshall
What problem does this solve? Dijkstra fails with negative edge weights. Bellman-Ford handles negative weights and detects negative cycles for single-source shortest paths. Floyd-Warshall computes shortest paths between ALL pairs of vertices in O(V³).
1. Bellman-Ford Algorithm
How It Works
Relax all edges |V| - 1 times. After k iterations, we've found shortest paths using at most k edges. Since the shortest path in a graph without negative cycles has at most |V| - 1 edges, |V| - 1 iterations suffice.
pseudoFor i = 1 to |V| - 1: For each edge (u, v, w): if dist[u] + w < dist[v]: dist[v] = dist[u] + w
Implementation
python# runnable def bellman_ford(graph, V, s): """Bellman-Ford: single-source shortest paths. graph = list of (u, v, weight) Returns (distances, has_negative_cycle) Time: O(V × E) """ INF = float('inf') dist = [INF] * V dist[s] = 0 # Relax all edges V-1 times for _ in range(V - 1): for u, v, w in graph: if dist[u] != INF and dist[u] + w < dist[v]: dist[v] = dist[u] + w # Check for negative cycles for u, v, w in graph: if dist[u] != INF and dist[u] + w < dist[v]: return dist, True # Negative cycle detected! return dist, False # Test edges = [ (0, 1, 4), (0, 2, 5), (1, 2, -3), (1, 3, 3), # Negative weight! (2, 3, 2), (2, 4, 4), (3, 4, 1) ] dist, has_neg = bellman_ford(edges, 5, 0) print(f"Distances: {dist}") # [0, 4, 1, 3, 4] print(f"Has negative cycle: {has_neg}") # Negative cycle example neg_cycle = [ (0, 1, 5), (1, 2, -10), (2, 1, 3) # Cycle 1→2→1: -10 + 3 = -7 ] dist2, has_neg2 = bellman_ford(neg_cycle, 3, 0) print(f"Neg cycle test: {has_neg2}") # True
2. Bellman-Ford with Adjacency List
python# runnable def bellman_ford_list(WList, s): """Bellman-Ford using adjacency list.""" INF = float('inf') distance = {v: INF for v in WList} distance[s] = 0 V = len(WList) for _ in range(V - 1): for u in WList: for v, d in WList[u]: if distance[u] != INF and distance[u] + d < distance[v]: distance[v] = distance[u] + d # Check negative cycles for u in WList: for v, d in WList[u]: if distance[u] != INF and distance[u] + d < distance[v]: return distance, True return distance, False WL = { 0: [(1, 4), (2, 5)], 1: [(2, -3), (3, 3)], 2: [(3, 2), (4, 4)], 3: [(4, 1)], 4: [] } dist_list, neg = bellman_ford_list(WL, 0) print(f"List-based: {dist_list}")
3. Floyd-Warshall Algorithm — All-Pairs Shortest Paths
How It Works
Dynamic programming: let
SP^k[i][j] = shortest path from i to j using only vertices {0, 1, ..., k-1}.python# runnable def floyd_warshall(WMat): """Floyd-Warshall: all-pairs shortest paths. Time: O(V³) Space: O(V²) """ V, _, _ = WMat.shape INF = float('inf') # Initialize: direct edges SP = [[INF] * V for _ in range(V)] for i in range(V): SP[i][i] = 0 for j in range(V): if WMat[i, j, 0] == 1: SP[i][j] = WMat[i, j, 1] # DP: consider each vertex as intermediate for k in range(V): for i in range(V): for j in range(V): SP[i][j] = min(SP[i][j], SP[i][k] + SP[k][j]) # Check negative cycles: if SP[i][i] < 0, there's a negative cycle for i in range(V): if SP[i][i] < 0: return SP, True # Negative cycle return SP, False # Test WM = np.zeros(shape=(4, 4, 2)) edges = [(0, 1, 3), (0, 3, 7), (1, 2, 1), (1, 3, 4), (2, 0, 2), (2, 3, 5)] for (i, j, w) in edges: WM[i, j, 0] = 1 WM[i, j, 1] = w import numpy as np SP, has_neg = floyd_warshall(WM) print("Shortest paths:") for row in SP: print(row)
Step-by-Step Trace
pseudoInitial: 0 3 ∞ 7 ∞ 0 1 4 2 ∞ 0 5 ∞ ∞ ∞ 0 After k=0: 0 3 ∞ 7 ∞ 0 1 4 2 5 0 5 (2→0→1 = 2+3=5) ∞ ∞ ∞ 0 After k=1: 0 3 4 7 (0→1→2 = 3+1=4) ∞ 0 1 4 2 5 0 5 ∞ ∞ ∞ 0 After k=2: ... Final: 0 3 4 7 3 0 1 4 2 5 0 5 ∞ ∞ ∞ 0
4. Comparison
| Feature | Bellman-Ford | Floyd-Warshall | Dijkstra |
|---|---|---|---|
| Problem | Single-source | All-pairs | Single-source |
| Negative weights | ✅ Handles | ✅ Handles | ❌ |
| Negative cycles | ✅ Detects | ✅ Detects | ❌ |
| Time | O(VE) | O(V³) | O(V²) or O((V+E)log V) |
| Space | O(V) | O(V²) | O(V) |
Practice Questions
Q1. Why does Bellman-Ford need exactly V-1 iterations?
Q2. How does Floyd-Warshall detect negative cycles?
Q3. After running Floyd-Warshall, SP[i][j] = ∞. What does this mean?
AnswersA1. The shortest path in a graph with |V| vertices and no negative cycles has at most |V|-1 edges. Each iteration discovers paths using one more edge. After |V|-1 iterations, we've found all shortest paths.A2. If after the algorithm completes, SP[i][i] < 0, there's a negative cycle reachable from i (since the shortest path from i to itself should be 0).A3. There's no path from i to j — the two vertices are in different connected components (considering direction). Join Discord Previous23. Dijkstra's Shortest Path AlgorithmNext25. Minimum Spanning Trees — Prim & Kruskal