3.5.11 · D3Graphs

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

2,491 words11 min readBack to topic

The scenario matrix

Every Bellman-Ford problem lands in one of these cells. Our examples below are labelled with the cell they hit, so together they cover the whole table.

# Case class What's special Example
A All-positive edges Dijkstra would also work; sanity baseline Ex 1
B Negative edge, no cycle The reason Bellman-Ford exists Ex 2
C Order matters in a pass Bad edge order needs full passes Ex 3
D Negative cycle reachable from source Detection must fire Ex 4
E Negative cycle not reachable from source Detection must stay silent Ex 5
F Unreachable vertex () The dist[u] != inf guard Ex 6
G Degenerate: 1 vertex, 0 edges Base case only Ex 7
H Zero-weight cycle (limiting between and ) Must not be flagged Ex 8
I Real-world word problem Currency / travel tolls Ex 9
J Exam twist: "which pass first settles ?" Reasoning about convergence speed Ex 10

Note the two limiting boundaries we deliberately test: cell H sits exactly on the fence (cycle sum , neither negative nor positive) and cell G is the smallest legal graph. Never skip the boundaries — that is where wrong code dies.


Ex 1 — Cell A: all-positive baseline

Forecast: guess the four final numbers before reading on. (Hint: is going cheaper than direct?)

Figure — Bellman-Ford algorithm — DP approach, negative cycles detection, O(VE)
  1. Init. dist = [0, ∞, ∞, ∞]. Why this step? With edges you can only sit at the source, cost ; everything else is unknown, i.e. .
  2. Pass 1, edges in listed order.
    • : dist[1]=2.
    • : dist[2]=6.
    • : dist[2]=5. (the detour wins!)
    • : dist[3]=7.
    • : dist[3]=6. Why this step? One sweep already propagated cost outward two hops because the edges happened to be in a friendly order.
  3. Pass 2. Check every edge again → nothing improves. Why this step? When a full pass changes nothing, the early-exit break fires; distances are final.

Answer: dist = [0, 2, 5, 6].

Verify: Cheapest to is (beats direct ). Cheapest to is (beats ). ✓


Ex 2 — Cell B: negative edge, no cycle

Forecast: the road pays you . Does that make reaching cheaper than the direct ?

  1. Init. dist = [0, ∞, ∞, ∞].
  2. Pass 1.
    • : dist[1]=4.
    • : dist[2]=5.
    • : dist[2]=1. Why? the negative edge undercuts the direct route.
    • : dist[3]=3.
  3. Pass 2. No edge improves → converged. Why this step? Confirms nothing negative loops back; this is a legit finite answer.

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

Verify: ; then . No cycle exists (edges only go "forward"), so distances are meaningful. ✓ This is the exact scenario Dijkstra would mishandle, because it would finalize dist[2]=5 before ever seeing the cheaper negative-edge route — see Dijkstra's Algorithm.


Ex 3 — Cell C: edge order forces multiple passes

Forecast: same final answer as Ex 2 — but how many passes until it settles now?

Figure — Bellman-Ford algorithm — DP approach, negative cycles detection, O(VE)
  1. Pass 1 (this bad order):
    • : dist[2] is → guard blocks it, no change.
    • : dist[1] is → blocked.
    • : dist[2]=5.
    • : dist[1]=4. Result after pass 1: [0, 4, 5, ∞]. Why worse? The edges that use new info came before the edges that produced it.
  2. Pass 2.
    • : dist[3]=7.
    • : dist[2]=1.
    • , : no change. Result: [0, 4, 1, 7].
  3. Pass 3.
    • : dist[3]=3. Result: [0, 4, 1, 3]. Now it matches Ex 2.

Answer: dist = [0, 4, 1, 3] — identical, but it needed 3 passes () instead of 1.

Verify: Same numbers as Ex 2 ✓. This is why we never trust a lucky order: worst case needs the full passes. The final answer is order-independent; the speed is not.


Ex 4 — Cell D: negative cycle reachable → DETECT

Forecast: loop sums to . Can any finite answer survive?

Figure — Bellman-Ford algorithm — DP approach, negative cycles detection, O(VE)
  1. Init. dist = [0, ∞, ∞]. Here , so we do settle passes.
  2. Pass 1. : dist[1]=1. : dist[2]=0. : dist[0]=-1. Why this step? The cycle already dragged the source's own cost below zero — a red flag.
  3. Pass 2. Everything drops again: dist[1]=0, dist[2]=-1, dist[0]=-2. Why this step? Each lap of the loop subtracts ; the values never stop.
  4. Extra pass (the ). : still relaxes (? recompute with current) → some edge still improves. Why this step? After passes everything that can settle has settled. A still-relaxable edge can only mean a negative cycle.

Answer: return Nonenegative cycle reachable from source.

Verify: Cycle weight . Looping it times gives cost , so no finite shortest distance exists → the flag is correct, not the numbers. See Negative Weight Cycles. ✓


Ex 5 — Cell E: negative cycle NOT reachable → stay silent

Forecast: there is a negative cycle in the graph. Will Bellman-Ford flag it?

  1. Init. dist = [0, ∞, ∞, ∞].
  2. All passes. Only ever relaxes (dist[1]=2). Edges and have dist[2]=dist[3]=∞ → guard blocks them forever. Why this step? Source has no path into , so their distances stay and the loop is never entered.
  3. Extra pass. No edge relaxes → no cycle reported.

Answer: dist = [0, 2, ∞, ∞]. Cycle not detected.

Verify: Bellman-Ford only detects cycles reachable from the source — this is the second parent-note mistake made concrete. To catch all cycles you'd add a virtual source with -weight edges to every vertex (then become reachable). ✓


Ex 6 — Cell F: the dist[u] != inf guard in action

Forecast: without the guard, would that huge edge corrupt dist[1]?

  1. Init. dist = [0, ∞, ∞].
  2. Relax : dist[1]=3.
  3. Relax : dist[2] is . Computing is still , and is false, so no change even without a guard in Python — but in a fixed-width integer language, "INF + (−100)" can wrap around to a huge negative and wrongly overwrite dist[1]. Why this step? The explicit if dist[u] != inf check exists to stop that overflow bug.

Answer: dist = [0, 3, ∞].

Verify: has no incoming path from source, so it stays ; 's only reachable route is the direct . ✓


Ex 7 — Cell G: degenerate single vertex

Forecast: how many passes is here?

  1. Init. dist = [0].
  2. Settle passes: passes → the loop body never runs.
  3. Extra pass: no edges → nothing relaxes → no cycle.

Answer: dist = [0].

Verify: The distance from a node to itself is ; with no edges nothing else exists. The boundary correctly does zero work. ✓ This confirms the loop bound is inclusive-safe at the smallest input.


Ex 8 — Cell H: zero-weight cycle (the exact fence)

Forecast: a -cost loop is neither profit nor loss. Should the detector fire?

Figure — Bellman-Ford algorithm — DP approach, negative cycles detection, O(VE)
  1. Init. dist = [0, ∞, ∞]. → 2 settle passes.
  2. Pass 1. : dist[1]=2. : dist[2]=0. : , and is false → no change.
  3. Pass 2. No edge improves.
  4. Extra pass. : ? No. Nothing relaxes → no cycle reported.

Answer: dist = [0, 2, 0], no cycle flag.

Verify: A -weight loop leaves distances unchanged after one lap, so dist[u]+w < dist[v] is never strictly satisfied. Bellman-Ford flags strictly negative cycles only — the strict < is exactly what keeps the fence-case silent. ✓


Ex 9 — Cell I: real-world word problem

Forecast: which is better, (rate ) or (rate )? The bigger product = the more negative summed toll.

  1. Model. Turn "multiply rates" into "add tolls" via . Why this step? Bellman-Ford adds weights; logarithms turn products into sums, so a profitable path = a smallest (most negative) sum. A negative cycle here would mean arbitrage (free money looping trades).
  2. Init. dist = [0, ∞, ∞] for .
  3. Relax. : dist[B] = -ln 2 ≈ -0.6931. : -ln2 - ln3 = -ln6 ≈ -1.7918. : -ln5 ≈ -1.6094. Since , keep dist[C] = -ln6.
  4. Extra pass. No cycle (edges only go forward) → no arbitrage.

Answer: best route with summed toll , i.e. rate (beats direct ).

Verify: and , so the two-hop trade genuinely yields more currency. The chosen path corresponds to the smallest weight, exactly what shortest-path minimises. ✓ (Arbitrage detection via negative cycles is a classic Bellman-Ford application — see Shortest Path Problem.)


Ex 10 — Cell J: exam twist "which pass first settles ?"

Forecast: with the worst possible order, information crawls one edge per pass — so guess a number near .

  1. Pass 1. Only can fire (all others start from ): dist[1]=1. Reach = 1 hop.
  2. Pass 2. fires: dist[2]=2. Reach = 2 hops.
  3. Pass 3. dist[3]=3. Pass 4. dist[4]=4. Why this step? With reversed order, each pass pushes the "frontier" exactly one edge forward, so vertex at depth settles on pass .

Answer: dist[4]=4 first correct on pass 4 (, the worst case). Fewer passes would leave it .

Verify: Longest shortest-path here has edges . The reversed order is the pathological one that actually needs all passes — proving the bound is tight, not loose. ✓


Recall Which cell trips you up?

Ex 4 vs Ex 5 — both contain a negative cycle. Why does only one get flagged? Only Ex 4's cycle is reachable from the source; Ex 5's cycle sits in an unreachable component so its vertices stay and its edges are never relaxed.

Ex 4 vs Ex 8 — both have a cycle. Why does only Ex 4 flag? Ex 4's cycle sums to (strictly negative → relax fires forever); Ex 8's sums to (the strict < never triggers).


Recall checks


Connections

  • Dijkstra's Algorithm — would fail Ex 2 by finalizing the wrong dist[2]
  • Dynamic Programming — every pass = advancing (edges used) in the DP
  • Floyd-Warshall — the all-pairs cousin, also survives negatives
  • Shortest Path Problem · Negative Weight Cycles · Graph Relaxation · SPFA