3.5.11Graphs

Bellman-Ford algorithm — DP approach, negative cycles detection, O(VE)

1,761 words8 min readdifficulty · medium6 backlinks

WHAT is Bellman-Ford?

WHY do we need it (when Dijkstra exists)? Dijkstra assumes once you "finalize" a node, no future path can beat it — true only if all edges are 0\ge 0. A negative edge can make a longer-hop path cheaper later, breaking Dijkstra's greedy promise. Bellman-Ford makes no greedy commitment; it just keeps relaxing until nothing improves.


HOW: the DP derivation from scratch

Let me derive it, not memorize it.

Define the subproblem: D[k][v]=shortest distance from source s to v using at most k edges.D[k][v] = \text{shortest distance from source } s \text{ to } v \text{ using at most } k \text{ edges.}

Base case (WHY): with 00 edges you can only stay at ss. D[0][s]=0,D[0][v]=  (vs)D[0][s] = 0, \qquad D[0][v] = \infty \ \ (v \ne s)

Transition (WHY): a path to vv using k\le k edges either:

  • uses k1\le k-1 edges (don't add anything), or
  • takes a path to some neighbor uu in k1\le k-1 edges, then the edge (u,v)(u,v).

D[k][v]=min(D[k1][v], min(u,v)E(D[k1][u]+w(u,v)))D[k][v] = \min\Big(D[k-1][v],\ \min_{(u,v)\in E}\big(D[k-1][u] + w(u,v)\big)\Big)

WHY stop at k=V1k = V-1? A shortest path with no negative cycle is simple (no repeated vertex), so it has at most VV vertices and hence at most V1V-1 edges. So D[V1][v]D[V-1][v] is the true answer.

Relaxation = the transition in disguise

We don't need the 2D table. Keep one array dist[] and "relax":

relax(u, v, w):  if dist[u] + w < dist[v]:  dist[v] = dist[u] + w

Running one full pass over all edges = advancing kk by (at least) one. Doing V1V-1 passes guarantees convergence.

Figure — Bellman-Ford algorithm — DP approach, negative cycles detection, O(VE)

HOW: negative cycle detection


Pseudocode (full)

def bellman_ford(V, edges, src):
    dist = [float('inf')] * V
    dist[src] = 0
    # V-1 relaxation passes
    for _ in range(V - 1):
        updated = False
        for u, v, w in edges:
            if dist[u] != float('inf') and dist[u] + w < dist[v]:
                dist[v] = dist[u] + w
                updated = True
        if not updated:           # early exit optimization
            break
    # one extra pass -> detect negative cycle
    for u, v, w in edges:
        if dist[u] != float('inf') and dist[u] + w < dist[v]:
            return None           # negative cycle reachable
    return dist

Why the dist[u] != inf guard? inf + w is still inf; without the guard you'd never wrongly relax, but it avoids spurious updates and overflow in languages with fixed-width ints.


Worked Example 1 — normal graph with a negative edge

Vertices 0,1,2,30,1,2,3; edges: 0 ⁣ ⁣1(4), 0 ⁣ ⁣2(5), 1 ⁣ ⁣2(3), 2 ⁣ ⁣3(2)0\!\to\!1\,(4),\ 0\!\to\!2\,(5),\ 1\!\to\!2\,(-3),\ 2\!\to\!3\,(2). Source =0=0.

pass dist[0] dist[1] dist[2] dist[3]
init 0
1 0 4 1 (via 1→2, 434{-}3) 3
2 0 4 1 3
  • Why does pass 1 give dist[2]=1 not 5? Order matters: we relaxed 010\to1 first (dist[1]=4), then 121\to2 used it → 43=14-3=1, beating the direct 55.
  • Why does pass 2 change nothing? Already converged; the early-exit break triggers. V1=3V-1=3 passes weren't all needed.

Answer: [0,4,1,3][0,4,1,3].

Worked Example 2 — negative cycle

Edges: 0 ⁣ ⁣1(1), 1 ⁣ ⁣2(1), 2 ⁣ ⁣0(1)0\!\to\!1\,(1),\ 1\!\to\!2\,(-1),\ 2\!\to\!0\,(-1). Source =0=0.

Cycle 01200\to1\to2\to0 has weight 111=1<01-1-1=-1<0.

  • After V1=2V-1=2 passes, dist keeps dropping.
  • Why detection fires: the extra pass still relaxes some edge (e.g. 202\to0 improves dist[0]). → report negative cycle.

Why this step matters: distances are meaningless here, so we must return a flag, not numbers.


Common mistakes


Recall Feynman: explain to a 12-year-old

Imagine prices to travel between cities, where some roads pay you (negative cost). You want the cheapest way from home to every city. You go around updating "cheapest known cost so far" by checking every road again and again. After checking the whole map (number of cities − 1) times, the prices stop dropping — that's your answer. But if there's a loop of roads that pays you net money each lap, your cost never stops dropping (free money forever!). So if one more full check still lowers a price, you shout "there's a money-loop!" — that's a negative cycle.


Recall checks


Flashcards

Bellman-Ford time complexity
O(VE)O(V \cdot E)
Bellman-Ford DP subproblem D[k][v]D[k][v]
shortest dist from source to vv using at most kk edges
Why relax V1V-1 times
a simple shortest path has at most V1V-1 edges
Bellman-Ford transition
D[k][v]=min(D[k1][v], min(u,v)D[k1][u]+w(u,v))D[k][v]=\min(D[k-1][v],\ \min_{(u,v)}D[k-1][u]+w(u,v))
How to detect a negative cycle
after V1V-1 passes, do one more pass; if any edge still relaxes → negative cycle
Which negative cycles does it detect
only those reachable from the source
Why Dijkstra fails with negative edges
greedy finalization assumes no future cheaper longer-hop path; negative edges break that
Base case of Bellman-Ford DP
D[0][s]=0D[0][s]=0, D[0][v]=D[0][v]=\infty for vsv\ne s
Early-exit optimization
if a pass makes no update, stop — distances already converged
Trick to detect ALL negative cycles
add a virtual source with 0-weight edges to all vertices

Connections

  • Dijkstra's Algorithm — faster (O(ElogV)O(E\log V)) but needs non-negative edges
  • Dynamic Programming — Bellman-Ford is DP over "edges used"
  • Floyd-Warshall — all-pairs version, also handles negatives, O(V3)O(V^3)
  • Shortest Path Problem
  • Negative Weight Cycles
  • Graph Relaxation
  • SPFA — queue-based optimization of Bellman-Ford

Concept Map

breaks

motivates

derived from

initialized by

recurrence

simple path bound

implemented as

yields

check with

still relaxes

cost O of VE

Dijkstra greedy

Negative edges

Bellman-Ford

Subproblem D k v

Base case D 0 s = 0

Transition relax rule

Stop at k = V-1

Relax all E edges V-1 times

dist v = D V-1 v

One extra pass

Negative cycle detected

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Bellman-Ford ka idea simple hai: shortest path mein agar koi negative cycle nahi hai, to woh ek "simple path" hoga, yaani usme zyada se zyada V1V-1 edges honge. Isliye agar hum saare edges ko V1V-1 baar "relax" kar dein (relax matlab: agar dist[u]+w se dist[v] chhota ho jaaye to update kar do), to har node ki sahi shortest distance mil jaati hai. Yahi reason hai O(VE)O(V \cdot E) complexity ka — V1V-1 passes, har pass mein EE edges.

Dijkstra negative edges pe fail karta hai kyunki woh greedy hai — ek baar node finalize kar diya to maan leta hai ki future mein koi sasta raasta nahi aayega. Lekin negative edge baad mein ek lamba raasta sasta bana sakta hai. Bellman-Ford koi greedy commitment nahi karta, bas baar-baar relax karta rehta hai, isliye negative weights handle kar leta hai.

Negative cycle detection ka jugaad mast hai: V1V-1 passes ke baad sab kuch settle ho jaana chahiye. Ab agar ek extra pass mein bhi koi edge relax ho raha hai, iska matlab koi cycle hai jiska total weight negative hai — usme ghoom-ghoom ke distance infinite tak girta rahega, to finite shortest path exist hi nahi karta. Yaad rakhna: yeh sirf woh negative cycles pakadta hai jo source se reachable hain.

Formula yaad rakhne ka tareeka: "V-minus-one to relax, one more to react." Pehle V1V-1 passes distances nikaalte hain, aur last wala +1 pass sirf negative cycle ko react/detect karne ke liye hai. Exam aur interview dono mein yeh point bahut poocha jaata hai.

Go deeper — visual, from zero

Test yourself — Graphs

Connections