jongkwan.dev
Development · Essay №016

The Bellman-Ford Algorithm: Shortest Paths with Negative Weights

An algorithm that finds shortest paths in graphs with negative edge weights and detects negative cycles

Jongkwan Lee2025년 8월 16일6 min read
Contents

An algorithm that finds shortest paths in graphs with negative edge weights and detects negative cycles as well.

When computing shortest paths in a weighted graph, Dijkstra's algorithm no longer applies once edge weights can be negative. Bellman-Ford permits negative weights, still finds the shortest paths, and additionally detects whether a negative cycle exists.

What it solves

It finds the shortest path from a single source vertex to every other vertex. What separates it from Dijkstra is that it works even when some edge weights are negative.

Key properties

  • Works on graphs containing negative weights
  • Can detect the presence of a negative cycle
  • Time complexity: O(V×E)O(V \times E) (V is the vertex count, E the edge count)

Dijkstra vs Bellman-Ford

PropertyDijkstraBellman-Ford
Time complexityO(ElogV)O(E \log V)O(V×E)O(V \times E)
Negative weightsNot supportedSupported
Negative cycle detectionNot supportedSupported

The algorithm

Step 1: relaxation

Iterate over every edge V-1 times, updating distances as you go.

Relaxation is the operation of lowering a recorded distance whenever a shorter path to that vertex is discovered.

python
for i in range(V-1):
    for (u, v, w) in edges:
        if dist[u] + w < dist[v]:
            dist[v] = dist[u] + w

Why V-1 iterations: a shortest path contains at most V-1 edges.

Step 2: negative cycle detection

After the V-1 iterations, scan the edges one more time. If any distance still updates, a negative cycle exists.

python
for (u, v, w) in edges:
    if dist[u] + w < dist[v]:
        return "negative cycle detected"

Example graph

fromtoweight
016
027
128
135
14-4
23-3
249
31-2
402
437

Trace (source: 0)

  • Initial: [0, ∞, ∞, ∞, ∞]
  • After pass 1: [0, 2, 7, 4, 2]
  • After pass 2: [0, 2, 7, 4, -2]
  • After pass 3: [0, 2, 7, 4, -2]

Result: [0, 2, 7, 4, -2]

The intermediate values per pass come from relaxing in the edges order below; a different edge order produces different intermediate values. The final result is the same either way.

Python implementation

bellman_ford(start, n, edges) takes the source vertex start, the vertex count n, and the edge list edges. It initializes the distance array to INF, sets the source to 0, and returns a (dist, has_negative_cycle) tuple. When has_negative_cycle is True, the distance array cannot be trusted.

python
import sys
 
def bellman_ford(start, n, edges):
    INF = sys.maxsize
    dist = [INF] * n
    dist[start] = 0
 
    # V-1 iterations
    for _ in range(n - 1):
        for u, v, w in edges:
            if dist[u] != INF and dist[u] + w < dist[v]:
                dist[v] = dist[u] + w
 
    # negative cycle detection
    for u, v, w in edges:
        if dist[u] != INF and dist[u] + w < dist[v]:
            return dist, True  # negative cycle exists
 
    return dist, False
 
 
# Run
edges = [
    (0, 1, 6), (0, 2, 7), (1, 2, 8), (1, 3, 5),
    (1, 4, -4), (2, 3, -3), (2, 4, 9),
    (3, 1, -2), (4, 0, 2), (4, 3, 7)
]
 
dist, has_negative_cycle = bellman_ford(0, 5, edges)
 
if has_negative_cycle:
    print("Negative cycle detected")
else:
    print(f"Shortest distances: {dist}")

Java implementation

The logic matches the Python version, except that it throws an exception on a negative cycle instead of returning a boolean.

java
import java.util.*;
 
public class BellmanFord {
    static class Edge {
        int u, v, w;
        Edge(int u, int v, int w) {
            this.u = u; this.v = v; this.w = w;
        }
    }
 
    public static int[] bellmanFord(int start, int n, List<Edge> edges) {
        int[] dist = new int[n];
        Arrays.fill(dist, Integer.MAX_VALUE);
        dist[start] = 0;
 
        // V-1 iterations
        for (int i = 0; i < n - 1; i++) {
            for (Edge e : edges) {
                if (dist[e.u] != Integer.MAX_VALUE &&
                    dist[e.u] + e.w < dist[e.v]) {
                    dist[e.v] = dist[e.u] + e.w;
                }
            }
        }
 
        // negative cycle detection
        for (Edge e : edges) {
            if (dist[e.u] != Integer.MAX_VALUE &&
                dist[e.u] + e.w < dist[e.v]) {
                throw new RuntimeException("Negative cycle detected");
            }
        }
 
        return dist;
    }
}

Go implementation

Like the Python version it returns (dist, bool), using math.MaxInt32 in place of infinity.

go
func BellmanFord(start, n int, edges []Edge) ([]int, bool) {
    dist := make([]int, n)
    for i := range dist {
        dist[i] = math.MaxInt32
    }
    dist[start] = 0
 
    // V-1 iterations
    for i := 0; i < n-1; i++ {
        for _, e := range edges {
            if dist[e.u] != math.MaxInt32 &&
               dist[e.u]+e.w < dist[e.v] {
                dist[e.v] = dist[e.u] + e.w
            }
        }
    }
 
    // negative cycle detection
    for _, e := range edges {
        if dist[e.u] != math.MaxInt32 &&
           dist[e.u]+e.w < dist[e.v] {
            return dist, true
        }
    }
 
    return dist, false
}

When a negative cycle exists

The shortest distance to any vertex reachable from a negative cycle diverges to -\infty and is therefore undefined. (Every additional pass keeps lowering the value.)

Applications

FieldUse
Finance and economicsDetecting currency arbitrage opportunities
NetworkingRouting with negative costs
GamesPath computation with negative effects

Summary

  • V-1 passes to relax distances
  • One extra pass to decide whether a negative cycle exists
  • Time complexity O(V×E)O(V \times E), which is inefficient on large graphs
  • The default choice whenever negative weights are involved

Bellman-Ford settles the shortest distances in V-1 relaxation passes and identifies negative cycles with one additional check. Vertices reachable from a negative cycle have diverging distances, so they must be flagged separately. Its O(V×E)O(V \times E) complexity makes it slow on large graphs with many edges. When there are no negative weights Dijkstra is faster, so reach for Bellman-Ford when negative weights or negative cycle detection is actually required.