DAG Shortest Paths

Suppose a build system must schedule tasks where each task depends on others. Each dependency edge carries a cost. This might be the time to transfer build artifacts. It could also be a penalty or bonus for a scheduling choice. You want the cheapest (or slowest, or most expensive) way to get from a source task to every other task. Because the dependency graph has no cycles, this problem has an unusually clean solution. A single linear-time pass over the vertices in the right order finds all shortest paths at once. This "right order" is a Topological Sort of the directed acyclic graph (DAG). The technique of updating distance estimates edge by edge is Edge Relaxation and Optimal Substructure. Combined, they give an algorithm that is simpler, faster, and more general. It beats any shortest-path method that must handle cycles. It runs in Θ(V+E)\Theta(V + E) time. This is provably optimal, since that is the cost of merely reading the graph. Unlike Dijkstra's Algorithm or Bellman-Ford, it works correctly even when edge weights are negative. DAG shortest paths appear constantly in practice. This includes critical-path scheduling in project management (PERT charts) and instruction scheduling in compilers. It also includes longest-common-subsequence and other dynamic-programming problems reframed as graph traversals. Dependency resolution in build tools also fits this pattern. All of these reduce to shortest or longest paths in a DAG. Understanding this algorithm also builds direct intuition for why Dijkstra's algorithm needs a priority queue. Without acyclicity, there is no static order. In such a case, relaxing each edge once cannot guarantee correctness. This article gives the precise algorithm and a full correctness proof. The proof works by induction on topological order. It also includes a worked trace with negative edges and common pitfalls. Finally, it covers the historical context connecting it to the broader shortest-paths literature.

Key takeaways
  • DAG shortest paths are solved by relaxing edges in a topological order of the vertices — one linear pass.
  • Runs in Θ(V+E)\Theta(V+E) time and Θ(V)\Theta(V) space: strictly faster than Dijkstra's O((V+E)log⁡V)O((V+E)\log V).
  • Works correctly with negative edge weights, since no cycle can ever form to make a negative cycle.
  • Correctness follows by induction: when a vertex is processed, all its predecessors already hold final distances.
  • The same algorithm, with max⁡\max/−∞-\infty instead of min⁡\min/+∞+\infty, computes longest paths — the basis of critical-path scheduling (PERT/CPM).
  • It is a strict specialization of the general relax-and-repeat family that also includes Bellman-Ford and Dijkstra.
The core insight

If you process vertices in topological order, one relaxation pass per edge is enough to finalize every shortest-path distance — no revisiting, no priority queue, no restriction on edge signs.

The idea

A directed acyclic graph (DAG) is a directed graph with no directed cycles. This acyclicity is exactly what makes the single-source shortest-paths (SSSP) problem easy: in a DAG, vertices can be linearly ordered so that every edge points "forward" in the order. This ordering is a topological sort — a sequence v1,v2,…,vnv_1, v_2, \dots, v_n of the vertices such that for every edge (u,v)(u,v), uu precedes vv in the sequence. The key structural fact is this: if edges only point forward in an order, then by the time we reach vertex viv_i in that order, every edge entering viv_i comes from some vjv_j with j<ij < i, which has already been fully processed. This lets us finalize each vertex's shortest-path distance the moment we visit it, using edge relaxation: for an edge (u,v)(u,v) with weight w(u,v)w(u,v), we test whether reaching vv via uu improves on our current best estimate,

if d[v]>d[u]+w(u,v):d[v]←d[u]+w(u,v),π[v]←u.\text{if } d[v] > d[u] + w(u,v): \quad d[v] \leftarrow d[u] + w(u,v), \quad \pi[v] \leftarrow u.

The optimal substructure of shortest paths guarantees that a shortest path decomposes into shortest sub-paths of its prefixes; combined with the topological order, this means once all predecessors of vv have their final distances, one relaxation of every incoming edge is enough to determine vv's final distance. No vertex needs to be revisited, and there is no need to track "visited" sets or manage a priority queue as in Dijkstra's algorithm — the topological order is the priority order, computed once, up front, for free of any weight comparisons. Because correctness never depends on comparing or bounding edge weights (only on the order in which vertices are processed), negative weights cause no difficulty — a decisive advantage over Dijkstra's algorithm, which can produce wrong answers in the presence of negative edges.

How it works

The algorithm has three phases: 1. Topologically sort the vertices of the DAG (via DFS finish-time ordering or Kahn's algorithm), both O(V+E)O(V+E). 2. Initialize d[s]=0d[s] = 0, d[v]=+∞d[v] = +\infty for all v≠sv \neq s, and π[v]=NIL\pi[v] = \text{NIL}. 3. Process vertices in topological order; for each vertex uu, relax every outgoing edge (u,v)(u,v). Vertices unreachable from ss (topologically before ss, or in a disconnected part of the DAG) simply never get their dd-value improved and correctly remain at +∞+\infty. ``` DAG-SHORTEST-PATHS(G, w, s): topo ← TOPOLOGICAL-SORT(G) // Θ(V + E) for each vertex v in G.V: d[v] ← ∞ π[v] ← NIL d[s] ← 0 for each vertex u in topo, in order: // Θ(V) iterations for each edge (u, v) in G.Adj[u]: // Θ(E) total relaxations RELAX(u, v, w) return d, π

RELAX(u, v, w): if d[v] > d[u] + w(u, v): d[v] ← d[u] + w(u, v) π[v] ← u ``` If uu has not yet been reached (`d[u] = ∞`), its outgoing edges are still examined but never improve any `d[v]`, since ∞+w\infty + w stays ∞\infty (or is simply skipped as an optimization). After the single pass, `d[v]` holds the shortest-path weight from `s` to `v` for every `v`, and the predecessor pointers `π` let you reconstruct any shortest path by walking backward from a target to `s`.

Longest paths in a DAG (e.g., critical-path scheduling) use the identical algorithm with three sign flips: initialize `d[v] = -∞`, use `d[v] < d[u] + w(u,v)` as the relax condition, and take the vertex with maximum `d[v]` as the answer. Equivalently, negate all weights, run the shortest-path version, and negate the result back.

Figure
A DAG in topological order; relaxing edges left to right finalizes each d-value exactly once
s d=0 a d=5 b d=2 c d=3 d d=8 e d=6 5 2 -2 6 1 7 3 -2 Topological order: s, a, b, c, d, e — each d-value finalized left-to-right

Complexity & correctness

Time complexity. Topological sort takes Θ(V+E)\Theta(V+E) (a DFS with finish-time ordering, or Kahn's algorithm using in-degree counting). The main loop visits each vertex exactly once and, across all vertices, examines each edge exactly once (each edge (u,v)(u,v) is relaxed only when its tail uu is processed), giving Θ(V+E)\Theta(V + E) for the relaxation phase. Total time is

Θ(V)+Θ(V+E)+Θ(V+E)=Θ(V+E).\Theta(V) + \Theta(V+E) + \Theta(V+E) = \Theta(V+E).

This is asymptotically optimal: any correct algorithm must at least read every vertex and edge once, since an unexamined edge could hold an arbitrarily large negative weight that changes the answer.

Space complexity. Θ(V)\Theta(V) for the distance array dd, predecessor array π\pi, and the topological order, plus Θ(V+E)\Theta(V+E) for the input graph itself (typically not counted as "extra" space).

Correctness (proof by induction on topological order). Claim: immediately after vertex uu is processed (all its outgoing edges relaxed) in the main loop, d[u]d[u] already equals δ(s,u)\delta(s,u), the true shortest-path weight from ss to uu — provided uu is reachable from ss. Base case: the first vertex in topological order that could possibly be reachable is ss itself (any predecessor of ss would have to precede it in topological order, but if such a predecessor were reachable from ss there would be a cycle, contradicting acyclicity — so nothing "before" ss in the order can reach it via a path from ss). We initialize d[s]=0=δ(s,s)d[s] = 0 = \delta(s,s) directly. Inductive step: consider a vertex v≠sv \neq s reachable from ss, visited at some point in the topological order. By Edge Relaxation and Optimal Substructure, any shortest path s⇝vs \leadsto v ends with some edge (u,v)(u, v), and the prefix s⇝us \leadsto u must itself be a shortest path to uu (optimal substructure of shortest paths — a subpath of a shortest path is itself shortest). Since (u,v)(u,v) is an edge, uu precedes vv in the topological order, so by the inductive hypothesis d[u]=δ(s,u)d[u] = \delta(s,u) before vv is processed. Because the algorithm processes vertices in topological order, edge (u,v)(u,v) is relaxed at some point during uu's turn — strictly before vv is reached in the outer loop. That relaxation enforces

d[v]≤d[u]+w(u,v)=δ(s,u)+w(u,v)=δ(s,v).d[v] \le d[u] + w(u,v) = \delta(s,u) + w(u,v) = \delta(s,v).

Standard relaxation invariants also give d[v]≥δ(s,v)d[v] \ge \delta(s,v) always (relaxation never produces a distance shorter than a real path's weight, by induction on the number of relaxations along any path). Hence d[v]=δ(s,v)d[v] = \delta(s,v) exactly when vv's own outgoing edges are processed, completing the induction. ■\blacksquare

Why acyclicity is essential. In a general graph, no linear order can guarantee that all predecessors of every vertex are finalized before that vertex is processed — cycles mean some predecessor relationship is unavoidably "backward" for any fixed ordering. That is precisely why general graphs need either repeated relaxation passes bounded by path length (Bellman-Ford, O(VE)O(VE)) or a greedy priority-queue order that is only valid without negative edges (Dijkstra, O((V+E)log⁡V)O((V+E)\log V)). The DAG algorithm sidesteps both costs because the topological order is a valid processing order by definition of acyclicity.

Tradeoffs. The method is strictly less general than Bellman-Ford (it requires acyclicity) but strictly faster and simpler, and unlike Dijkstra it tolerates negative weights without any special handling — because a negative cycle, the only thing that truly breaks shortest paths, cannot exist in a DAG.

Worked example

Consider the DAG from the figure, with topological order s,a,b,c,d,es, a, b, c, d, e and edges:

EdgeWeight
(s,a)(s,a)5
(s,b)(s,b)2
(a,c)(a,c)−2
(a,d)(a,d)6
(b,c)(b,c)1
(b,d)(b,d)7
(c,e)(c,e)3
(d,e)(d,e)−2

Initialize d[s]=0d[s]=0, all others =∞=\infty.

Step: processEdge relaxedTestUpdateResulting d[⋅]d[\cdot]
ss(s,a)(s,a)∞>0+5\infty > 0+5? yesd[a]=5, π[a]=sd[a]=5,\ \pi[a]=sa=5a=5
ss(s,b)(s,b)∞>0+2\infty > 0+2? yesd[b]=2, π[b]=sd[b]=2,\ \pi[b]=sb=2b=2
aa(a,c)(a,c)∞>5+(−2)=3\infty > 5+(-2)=3? yesd[c]=3, π[c]=ad[c]=3,\ \pi[c]=ac=3c=3
aa(a,d)(a,d)∞>5+6=11\infty > 5+6=11? yesd[d]=11, π[d]=ad[d]=11,\ \pi[d]=ad=11d=11
bb(b,c)(b,c)3>2+1=33 > 2+1=3? no—c=3c=3 (unchanged)
bb(b,d)(b,d)11>2+7=911 > 2+7=9? yesd[d]=9, π[d]=bd[d]=9,\ \pi[d]=bd=9d=9
cc(c,e)(c,e)∞>3+3=6\infty > 3+3=6? yesd[e]=6, π[e]=cd[e]=6,\ \pi[e]=ce=6e=6
dd(d,e)(d,e)6>9+(−2)=76 > 9+(-2)=7? no—e=6e=6 (unchanged)
ee(no outgoing edges)——done

Final result: d=[s:0, a:5, b:2, c:3, d:9, e:6]d = [s{:}0,\ a{:}5,\ b{:}2,\ c{:}3,\ d{:}9,\ e{:}6]. Reconstructing the shortest path to ee: π[e]=c\pi[e]=c, π[c]=a\pi[c]=a, π[a]=s\pi[a]=s, giving s→a→c→es \to a \to c \to e with total weight 5+(−2)+3=65 + (-2) + 3 = 6 — matching d[e]d[e]. Note this path uses a negative edge (a,c)(a,c); Dijkstra's algorithm, applied naively to this same graph, would greedily finalize bb (distance 2) before ever discovering that aa leads to a cheaper route to cc through a negative edge — and could produce wrong answers on graphs where negative edges create such reorderings. The DAG algorithm sidesteps this entirely because it never relies on distances to choose processing order.

Common pitfalls & traps

PitfallWhy it happensFix
Forgetting the topological sort step, relaxing in arbitrary/input orderRelies on adjacency-list order, which need not respect edgesAlways compute topological order explicitly first
Assuming the algorithm needs non-negative weightsConfusing this method with Dijkstra's requirementNegative weights are fine; only negative cycles are a problem, and DAGs have none
Not handling unreachable verticesLeaving stale or uninitialized dd-valuesInitialize all to +∞+\infty; leave them untouched if never relaxed
Using this algorithm on a graph with a cycleNo valid topological order exists; algorithm is inapplicableDetect cycles during topological sort (e.g., DFS back-edge check) and reject
Sign errors converting to longest pathsForgetting to flip both the comparison and initializationEither negate weights and reuse shortest-path code, or carefully flip ∞→−∞\infty \to -\infty and >> to <<
Relaxing edges from a vertex before its own dd-value is finalizedProcessing vertices out of topological orderStrictly follow the topological order computed once at the start

Pioneers & history

The DAG shortest-paths algorithm is a direct descendant of the broader dynamic-programming and shortest-paths tradition. Its clean linear-time form emerges naturally once topological sorting (attributed in modern form to A. B. Kahn, 1962) is combined with the relaxation technique that underlies Bellman-Ford and Dijkstra's algorithms.

Origins · 1 minRichard E. Bellman and how this was discovered
RE
Richard E. Bellman
1920–1984 · American applied mathematician who formalized dynamic programming and the principle of optimality underlying shortest-path relaxation

Bellman's 1958 paper "On a Routing Problem" introduced the relaxation-based algorithm for shortest paths with negative weights (later credited jointly with Lester Ford), establishing the recurrence structure — optimal substructure — that both the general Bellman-Ford algorithm and its DAG specialization exploit. His broader work on dynamic programming gave the theoretical language ("principle of optimality") for why processing subproblems in a valid dependency order guarantees correctness, a principle this article's topological-order proof directly instantiates.

History
1958
Bellman and Ford formalize relaxation-based shortest paths, tolerating negative weights.
1959
Dijkstra publishes his greedy priority-based shortest-path algorithm for non-negative weights.
1962
Kahn describes a linear-time algorithm for topological sorting via in-degree removal.
1962
AOA/PERT and CPM critical-path scheduling methods formalize longest paths in task DAGs.
1990
s: CLRS-style textbooks standardize the DAG-relaxation-in-topological-order algorithm as a distinct, named technique.

Where this matters

Build systems and dependency resolution. Tools like Make, Bazel, and package managers model tasks as a DAG of dependencies; computing the earliest possible completion time (or minimum cost to reach a target artifact) is exactly a longest- or shortest-path computation in topological order.

Compiler instruction scheduling. Basic blocks compiled into data-dependence DAGs use longest-path computations to find the critical path determining minimum schedule length on a pipelined processor.

Project management (PERT/CPM). Critical-path scheduling computes the longest path through a task-precedence DAG to determine the minimum project duration and identify which tasks have zero slack.

Dynamic programming reformulated as graph problems. Many DP recurrences (edit distance, longest common subsequence, matrix-chain multiplication) can be viewed as DAG shortest/longest-path problems over an implicit state graph, and this framing clarifies both correctness (optimal substructure) and complexity.

Version-control and data-lineage graphs. Commit graphs, data-provenance graphs, and workflow orchestration systems (e.g., Airflow-style DAG schedulers) use topological processing for scheduling and impact analysis, closely related in spirit to shortest-path relaxation. This algorithm is also the conceptual stepping stone to Dijkstra's Algorithm: once cycles are allowed, no fixed processing order works for all inputs, so Dijkstra replaces the static topological order with a dynamic greedy order — always expanding the currently-closest vertex — which is valid precisely when weights are non-negative.

:::quiz[In the inductive correctness proof, what guarantees that d[u] is already final when edge (u,v) is relaxed?]

  • u is visited before v in topological order, and the inductive hypothesis applies to u ✓
  • u always has the smallest distance among unvisited vertices
  • u has out-degree 1
  • The graph has no negative edges

Ask the tutor: How would this algorithm need to change if the DAG had multiple source vertices with no single starting point?

Why can't we just use Dijkstra's algorithm on a DAG instead?

Dijkstra's algorithm works on DAGs with non-negative weights, but it is strictly more expensive: O((V+E)log⁡V)O((V+E)\log V) with a binary heap versus Θ(V+E)\Theta(V+E) here, because Dijkstra must maintain and query a priority queue to decide processing order. More importantly, Dijkstra's greedy choice — always finalize the currently closest unvisited vertex — is provably incorrect if any edge is negative, since a vertex could later be reached more cheaply through an edge from a vertex not yet finalized. The DAG algorithm avoids this entirely by using topological order, which is valid for any edge weights, positive or negative, as long as the graph is acyclic.

Does the algorithm work if the source vertex has incoming edges?

Yes, as long as those incoming edges come from vertices that appear earlier in the topological order — which they always must, by definition of topological sort and acyclicity. Any predecessor of ss is simply processed before ss in the loop; if that predecessor is itself unreachable from ss (which it typically is, since reaching a predecessor of ss from ss would require a cycle), its dd-value stays ∞\infty and its relaxations toward ss never fire. The algorithm handles this transparently with no special case.

What happens if some vertices are unreachable from the source?

Their distance values remain +∞+\infty (or −∞-\infty for longest paths) throughout, since no relaxation ever successfully updates them — every relaxation attempt from a finite-distance vertex into an unreachable one only occurs if there's an edge, and if there were such an edge the vertex wouldn't be unreachable. This is the expected, correct behavior: +∞+\infty correctly encodes "no path exists" and requires no special-casing in the pseudocode.

Can this algorithm detect if the input graph actually has a cycle?

Not by itself — it assumes an already-acyclic graph, because it depends on a topological sort existing. However, the topological sort subroutine typically does detect cycles: a DFS-based sort finds a back edge, or Kahn's algorithm finds that fewer than VV vertices are ever dequeued (some vertices never reach in-degree zero). A robust implementation should propagate this cycle-detection failure and refuse to run the shortest-paths phase, since results on a cyclic graph would be meaningless or the topological order would be ill-defined.

How does this connect to dynamic programming?

Directly — this algorithm is a dynamic program. The topological order is precisely a valid evaluation order for a DP recurrence where d[v]=min⁡(u,v)∈E(d[u]+w(u,v))d[v] = \min_{(u,v) \in E} (d[u] + w(u,v)), and "process vertices in topological order, relaxing outgoing edges" is equivalent to "fill in a DP table in an order that respects dependencies." Recognizing a DP recurrence's implicit dependency graph as a DAG, and its recurrence as edge relaxation, is a common and powerful technique for both implementing and reasoning about dynamic programs.

What if there are multiple shortest paths with the same total weight?

The algorithm as written finds one shortest-path weight per vertex and one valid predecessor tree, but not necessarily all shortest paths. Because relaxation only updates π[v]\pi[v] on strict improvement (`>`), ties are broken by whichever edge happens to be examined first; if you need to enumerate all shortest paths of equal weight, you must modify relaxation to store a set of predecessors when a new candidate equals (not just beats) the current d[v]d[v].

Does this algorithm generalize to all-pairs shortest paths in a DAG?

Yes, by running the single-source version from every vertex as source, giving Θ(V(V+E))=Θ(V2+VE)\Theta(V(V+E)) = \Theta(V^2 + VE) total time — often better than the general-purpose Floyd-Warshall's Θ(V3)\Theta(V^3) for sparse DAGs where E=O(V)E = O(V). This is a good example of how exploiting known structure (acyclicity) can beat general-purpose algorithms even when solving a strictly harder version of the problem.

Before this

Where this leads