Shortest Path Algorithms

Dijkstra's & Bellman-Ford

Watch the shortest-path cloud expand from source A

Use arrow keys or buttons to navigate

The Shortest Path Problem

Given a weighted graph, find the path with the minimum total weight.

  • Edges have numeric weights (costs, distances, times)
  • "Shortest" = lowest sum of edge weights
  • Not necessarily the fewest edges!

Analogy: GPS Navigation

Your GPS finds the route with the least total travel time, not fewest turns. Each road segment has a "weight" (time).

Click any two nodes to see all paths and the shortest one highlighted.

Single-Source vs All-Pairs

Single-Source Shortest Path (SSSP)

From one source to all other vertices.

  • Dijkstra's — greedy, fast, no negative weights
  • Bellman-Ford — slower, handles negative weights

All-Pairs Shortest Path (APSP)

Between every pair of vertices.

  • Floyd-Warshall — O(V³), dynamic programming
  • Or: run Dijkstra from every vertex

Our Focus

This deck covers single-source algorithms: Dijkstra's and Bellman-Ford — the most common shortest-path scenario.

Greedy Approach: Dijkstra's Algorithm

Invented by Edsger Dijkstra in 1956. The core idea is a greedy strategy:

  • Maintain a set of vertices whose shortest distance is finalized
  • Always pick the unvisited vertex with the smallest known distance
  • Use it to relax (improve) distances to its neighbors
  • Mark as finalized and repeat

Analogy: Expanding Cloud

A "cloud" of certainty expands from the source. The closest unvisited vertex joins the cloud. Once inside, its shortest distance is guaranteed correct.

Dijkstra's Algorithm — Pseudocode

DIJKSTRA(G, source): for each vertex v in G: dist[v] = INFINITY parent[v] = NULL visited[v] = false dist[source] = 0 PQ.insert(source, 0) while PQ is not empty: u = PQ.extractMin() if visited[u]: continue visited[u] = true for each neighbor v of u: w = weight(u, v) if dist[u]+w < dist[v]: dist[v] = dist[u]+w parent[v] = u PQ.insert(v, dist[v]) return dist[], parent[]

Three Key Data Structures

dist[] — best-known distance  |  parent[] — predecessor  |  PQ — min-priority queue

Edge Relaxation

The fundamental operation in shortest-path algorithms

Relaxing edge (u, v) means checking:

Is dist[u] + w(u,v) < dist[v] ?

If yes: shorter path found via u → update!

If no: current path to v is already better.

Why "Relaxation"?

Think of dist[v] as an overestimate that we gradually "relax" (tighten) downward until it reaches the true shortest distance.

Try It!

Dijkstra's Step-by-Step

Source = A  |  Edges: A-B:4, A-C:2, A-D:1, B-D:3, C-D:5, D-E:2

Processing Order

Vertices are processed in increasing order of their shortest distance from the source.

Reconstructing the Path

Dijkstra gives us the distance to every vertex. To find the actual path, trace the parent[] array backwards.

PATH(parent, dest): path = empty stack v = dest while v != NULL: path.push(v) v = parent[v] return path // pop for order

Key Idea

The parent pointers form a tree rooted at the source. Every path in this tree is a shortest path.

Click any destination node to trace the path back to A.

Dijkstra's Correctness

Why the greedy choice is safe

The Claim

When Dijkstra's extracts vertex u from the PQ, dist[u] equals the true shortest-path distance.

Proof Sketch (by contradiction)

  • Suppose vertex u is the first vertex extracted with wrong distance
  • True shortest path: S → ... → x → y → ... → u where y is first unvisited
  • Since x is visited: dist[y] ≤ true dist to y
  • Since all weights ≥ 0: dist[y] ≤ true dist to u
  • But u extracted before y: dist[u] ≤ dist[y]
  • So dist[u] ≤ true dist to u → contradiction!

Critical Assumption

This proof requires all edge weights ≥ 0. Negative edges break the "cloud" property.

Dijkstra's Time Complexity

With Binary Heap (Min-Priority Queue)

OperationCountCostTotal
extractMinVO(log V)O(V log V)
insert/decreaseKeyEO(log V)O(E log V)

Total: O((V + E) log V)

For connected graphs (E ≥ V), simplifies to O(E log V).

With Simple Array (No Heap)

OperationCountCostTotal
findMin (scan)VO(V)O(V²)
update distEO(1)O(E)

Total: O(V²)

Simpler but slower. Better only for dense graphs (E ≈ V²).

Dijkstra's Limitation: Negative Weights

Dijkstra FAILS with Negative Edges!

Once a vertex is finalized, Dijkstra never reconsiders it. A negative edge can create a shorter path after finalization.

The Problem

Dijkstra finalizes B with dist=1, but the path A→C→B costs -1. Once finalized, B is never reconsidered.

Dijkstra: dist[B]=1, dist[D]=6
Correct: dist[B]=-1, dist[D]=4

Bellman-Ford Algorithm

Handles negative edge weights!

Core Idea

  • Relax ALL edges in each iteration
  • Repeat for V - 1 iterations
  • Each iteration extends correctness by one hop

Analogy: Ripple Effect

Like dropping a stone in a pond — each ripple extends correct distances by one hop. After V-1 ripples, every vertex is reached.

BELLMAN-FORD(G, source): for each v: dist[v]=∞, par[v]=NULL dist[source] = 0 for i = 1 to |V|-1: // V-1 iters for each edge (u,v,w): if dist[u]+w < dist[v]: dist[v] = dist[u]+w par[v] = u // Negative cycle check for each edge (u,v,w): if dist[u]+w < dist[v]: return "NEGATIVE CYCLE!"

Bellman-Ford Step-by-Step

Edges: (A,B,1) (A,C,3) (B,D,5) (C,B,-4) (C,D,1)

Bellman-Ford finds B=-1

Path A→C→B = 3+(-4) = -1. This is the correct answer that Dijkstra missed!

Detecting Negative Cycles

The Extra Pass

After V-1 iterations, do one more relaxation pass:

  • No distance decreases → all shortest paths correct
  • Any distance decreasesnegative cycle exists!

Why V-1 Suffices

A shortest path (without cycles) has at most V-1 edges. Each iteration correctly extends paths by one edge. If distances still decrease after V-1, a negative cycle must exist.

Negative Cycles Explained

What Is a Negative Cycle?

A cycle where the total weight is negative. Each trip around makes the cost lower — approaching -∞.

Why This Breaks Shortest Path

If you can reach a negative cycle from the source, and your destination is reachable from the cycle:

Path cost = prefix + n×(-3) + suffix
n=1: prefix + (-3) + suffix
n=10: prefix + (-30) + suffix
n=∞: -INFINITY

Analogy: Infinite Money Glitch

Currency exchange where USD→EUR→GBP→USD gives you more money. You'd loop forever for infinite profit. That's a negative cycle!

Important Distinction

Negative edges are fine (Bellman-Ford handles them). Negative cycles make the problem undefined.

Dijkstra vs Bellman-Ford

Choosing the right algorithm

When to Use Which?

  • Dijkstra — all weights ≥ 0, need speed → O(E log V)
  • Bellman-Ford — negative weights or cycle detection → O(V·E)

Challenge: Predict Dijkstra's Processing Order

Given this graph with source S, predict the order Dijkstra processes vertices.

Edges: S-A:3, S-B:7, S-C:5, A-B:1, A-D:6, C-D:2

Your Prediction

Enter the processing order (e.g., S,A,B,C,D):

Challenge: Fix the Bug

This Dijkstra implementation has a bug. What happens if we remove the visited check?

DIJKSTRA(G, source): dist[source] = 0; PQ.insert(source,0) while PQ not empty: u = PQ.extractMin() // BUG: missing visited check! // if visited[u]: continue // visited[u] = true for each neighbor v of u: if dist[u]+w < dist[v]: dist[v] = dist[u]+w PQ.insert(v, dist[v])

What's the consequence?

Challenge: Which Algorithm?

For each scenario, pick the best shortest-path algorithm.

1. GPS Navigation

Road distances are always positive. Need fastest route.

2. Currency Arbitrage Detection

Exchange rates → negative log weights. Need to find profitable loops.

3. All-Pairs in Small Dense Graph

10 vertices, ~90 edges, need distances between every pair.

4. Network with Link Credits

Some links give credits (negative cost). No negative cycles guaranteed.

Real-World Applications

GPS Navigation

Google Maps, Waze use Dijkstra variants (A*, contraction hierarchies).

Network Routing (OSPF)

OSPF protocol uses Dijkstra to compute shortest paths between routers.

Game AI Pathfinding

A* (Dijkstra + heuristic) finds cheapest path avoiding walls, preferring grass.

Currency Arbitrage

Convert weights to -log(rate). Negative cycle = profit loop!
Bellman-Ford detects: USD→EUR→GBP→USD > 1.0 → arbitrage!

Summary & Cheat Sheet

Decision Flowchart

Common Exam Pitfalls

  • Using Dijkstra with negative weights
  • Forgetting V-1 iterations in Bellman-Ford
  • Confusing negative edges with negative cycles

Quiz: Shortest Path Fundamentals

Q1: Dijkstra's Complexity

With a binary heap on a sparse connected graph (E ≈ V)?

Q2: Bellman-Ford Iterations

How many iterations does Bellman-Ford's main loop run?

Q3: Negative Edge vs Cycle

Which makes shortest path undefined?

Quiz: Trace Bellman-Ford

Run Bellman-Ford on this graph (source = A).

Edge order: (A,B,6) (A,C,4) (B,C,-3) (B,D,2) (C,D,5)

After iteration 1, what is dist[]?

Quiz: Predict the Output

What does this Java code print?

int[] dist = {0, INF, INF, INF, INF}; int[][] edges = { {0,1,10}, {0,2,5}, {1,3,1}, {2,1,3}, {2,3,9}, {3,4,4} }; // Dijkstra with PQ PQ pq = new MinPQ(); pq.insert(0, 0); boolean[] visited = new boolean[5]; while (!pq.isEmpty()) { int u = pq.extractMin(); if (visited[u]) continue; visited[u] = true; for (edge : adj[u]) if (dist[u]+edge.w < dist[edge.v]){ dist[edge.v] = dist[u]+edge.w; pq.insert(edge.v, dist[edge.v]); } } print(Arrays.toString(dist));

Your Prediction

Enter the final dist[] array (5 values):