Back to Graph Theory Series

Part 9: Shortest Paths I — Dijkstra & Bellman-Ford

August 30, 2026 Wasil Zafar 19 min read

Every shortest-path algorithm in this series — BFS, Dijkstra, Bellman-Ford, and everything still to come — is the same relaxation idea wearing different clothes. This part is where we see the whole family tree at once.

Table of Contents

  1. Shortest-Path Fundamentals
  2. Relaxation as a Unifying Idea
  3. Choosing the Right Algorithm
  4. Difference Constraints
  5. A Glimpse of Specialized Techniques
  6. Exercises
  7. Conclusion & Next Steps

Shortest-Path Fundamentals

"Shortest path" is really five related questions, not one:

  • Single-source: distances from one vertex to every other vertex (BFS, Dijkstra, Bellman-Ford).
  • Single-destination: distances from every vertex to one target — solved by running a single-source algorithm on the reversed graph.
  • Single-pair: distance between one specific source and target — no asymptotically faster general algorithm exists than solving single-source and reading off one answer, though heuristics like A* (Part 10) exploit extra structure to prune the search.
  • All-pairs: distances between every pair of vertices (Floyd-Warshall, Johnson's — Part 10).

Shortest-Path Trees

Every single-source algorithm we've built (BFS's parent map, Dijkstra's and Bellman-Ford's relaxation-driven parent updates) actually computes more than just distances — it computes a shortest-path tree: a spanning tree (of the reachable vertices) where the tree-path from the source to any vertex $v$ is a shortest path to $v$ in the original graph. This is why reconstructing an actual shortest path, not just its length, is always just a matter of walking parent pointers backward from the target to the source.

Shortest-Path Tree (SPT) Overlay Bold green edges form a spanning tree containing exact shortest paths from source S w=2 w=5 w=3 w=1 w=2 w=10 S d=0 A d=2 B d=5 C d=5 D d=6 T d=8

Relaxation as a Unifying Idea

Every shortest-path algorithm in this series — without exception — is built from exactly one primitive operation, first named formally in Part 2:

$$\textbf{Relax}(u, v, w)\text{: if } d[u] + w(u,v) < d[v] \text{, set } d[v] \leftarrow d[u] + w(u,v) \text{ and } \pi[v] \leftarrow u$$

Anatomy of Edge Relaxation: Relax(u, v, w=2) 1. Before: d[u] + w = 4 + 2 < d[v] (10) weight w = 2 u d[u] = 4 v d[v] = 10 Shorter path found through u! 2. After: d[v] updated to 6, π[v] = u weight w = 2 u d[u] = 4 v d[v] = 6 Distance relaxed & parent pointer set

What differs between BFS, Dijkstra, and Bellman-Ford is purely the order in which edges get relaxed:

AlgorithmRelaxation orderHandles negative weights?
BFS (deep dive)layer by layer (implicit — all weights = 1)n/a (unweighted)
Dijkstra (deep dive)always the currently-closest unfinalized vertexno
Bellman-Ford (deep dive)every edge, every round, $V-1$ timesyes
DAG shortest paths (Part 7)topological order, single passyes (DAG only)

Key Insight

Dijkstra's algorithm is, in a precise sense, "Bellman-Ford with a smarter relaxation order" — a priority queue is really just a mechanism for guessing, correctly, which edge is safe to relax next without needing to revisit it later. That guess is only guaranteed safe when weights are non-negative, which is exactly why Dijkstra's algorithm and Bellman-Ford diverge exactly there.

Optimality Conditions

A clean way to verify a claimed set of distances $d[\cdot]$ without re-running any algorithm: $d[\cdot]$ is correct if and only if (1) $d[\text{source}] = 0$, (2) every edge $(u,v)$ satisfies $d[v] \leq d[u] + w(u,v)$ (no further relaxation is possible — the triangle inequality for shortest paths), and (3) every $d[v]$ with $v$ reachable equals the weight of some actual path. This is exactly the property Bellman-Ford's final "extra round" checks in reverse — if condition (2) is ever violated, the claimed distances are wrong.

Choosing the Right Algorithm

Which Shortest-Path Algorithm Should You Use?
flowchart TD
    A["What kind of graph?"] -->|"Unweighted"| B["BFS — O(V+E)"]
    A -->|"DAG"| C["Topological DP — O(V+E)"]
    A -->|"Weighted, non-negative"| D["Dijkstra — O((V+E) log V)"]
    A -->|"Weighted, negative allowed"| E["Bellman-Ford — O(VE)"]
    A -->|"All-pairs needed"| F["Floyd-Warshall or Johnson's (Part 10)"]
            

Difference Constraints

A genuinely surprising application: a system of inequalities of the form $x_j - x_i \leq c_{ij}$ (common in scheduling — "task $j$ must start at least $c_{ij}$ after task $i$") can be solved directly with Bellman-Ford. Build a graph with one vertex per variable, an edge $i \to j$ with weight $c_{ij}$ for every constraint, and a super-source connected to every vertex with weight-0 edges. Run Bellman-Ford from the super-source: the resulting distances $d[\cdot]$ are a valid assignment satisfying every constraint simultaneously, and a detected negative cycle means the constraint system is infeasible — no valid assignment exists at all.

System of Difference Constraints as a Weighted Digraph Inequality x_j - x_i ≤ c_ij maps directly to directed edge i → j with weight c_ij S Super Source w=0 w=0 x_C x_B x_A x_B - x_C ≤ -2 (w = -2) x_A - x_B ≤ -3 (w = -3) Bellman-Ford outputs shortest distances from S: x_C = 0, x_B = -2, x_A = -5
Worked Example

Turning "A must start ≥3 after B, B must start ≥2 after C" into a Graph

Constraints $x_A - x_B \leq -3$ and $x_B - x_C \leq -2$ become edges $B \to A$ (weight $-3$) and $C \to B$ (weight $-2$), plus a super-source $S$ with zero-weight edges to $A, B, C$. Running Bellman-Ford from $S$ gives one valid schedule (e.g., $x_C = 0, x_B = -2, x_A = -5$) — shift all values by a constant to make them non-negative if needed, since only the differences matter.

Difference ConstraintsScheduling

A Glimpse of Specialized Techniques

A few refinements worth knowing exist, each trading generality for speed in a specific setting: Dial's algorithm replaces Dijkstra's binary heap with an array of buckets when edge weights are small non-negative integers, achieving $O(V + E + W)$ where $W$ is the maximum weight. Bidirectional Dijkstra (the weighted cousin of bidirectional BFS from Part 5) searches from both the source and target simultaneously. Replacement paths ask "what's the shortest path if this one edge is removed?" for every edge on the current shortest path — useful for fault-tolerant routing. We'll meet A* — arguably the most important of these refinements — in full in Part 10.

Exercises

  1. Verify the optimality conditions by hand on the worked example from the Dijkstra deep dive — confirm every edge satisfies $d[v] \leq d[u] + w(u,v)$ for the final distances.
  2. Explain why single-destination shortest paths reduce to single-source shortest paths on the reversed graph, but this trick does not help for single-pair queries.
  3. Model the constraints $x - y \leq 2$, $y - z \leq 3$, $z - x \leq -6$ as a graph, run Bellman-Ford, and show the system is infeasible (hint: sum the three constraints — what do you get?).
  4. Challenge: Implement Dial's algorithm (bucket-based Dijkstra) for a graph with integer weights in $[0, 10]$, and compare its running time against a binary-heap Dijkstra on a large sparse graph.

Conclusion & Next Steps

Every shortest-path algorithm is relaxation with a different edge order, verifiable by the same three optimality conditions, and — via difference constraints — solves scheduling problems that don't look like graphs at all until you draw them as one. Single-source is done; next, we tackle the case where you need distances between every pair of vertices at once.

Next in the Series

In Part 10: Shortest Paths II — Floyd-Warshall, Johnson's & A*, we solve all-pairs shortest paths two different ways and meet the heuristic-guided search that powers GPS routing and video-game pathfinding.