Quiz 2

24. Bellman-Ford & Floyd-Warshall

873 words
4 min read
Python Week 1: the first filter for runtime behavior
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.
pseudo
For 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

pseudo
Initial:   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

FeatureBellman-FordFloyd-WarshallDijkstra
ProblemSingle-sourceAll-pairsSingle-source
Negative weights✅ Handles✅ Handles
Negative cycles✅ Detects✅ Detects
TimeO(VE)O(V³)O(V²) or O((V+E)log V)
SpaceO(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?
Answers
A1. 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
Document outline

Keep your place and jump directly to a heading.

Table of Contents
System Normal // Awaiting Context

Intelligence Hub

Navigate the knowledge graph to generate context. The Hub adapts dynamically to surface backlinks, related notes, and metadata insights.