Hello! In our last lesson, we saw how a topological sort provides the perfect evaluation order for a dynamic programming recurrence, which allowed us to efficiently count the number of paths in a DAG. We are now going to apply this powerful pattern to solve another fundamental problem.
This lesson focuses on finding the shortest paths from a single source vertex to all other vertices in a weighted Directed Acyclic Graph (DAG). You will learn to formulate this as a dynamic programming problem and solve it using the same core technique: processing vertices in topological order. This algorithm is not only elegant but also remarkably efficient, running in linear time——and, importantly, it correctly handles negative edge weights, a feature that many other shortest path algorithms lack.
1. Shortest Paths as a Dynamic Programming Problem
Just like counting paths, the problem of finding the shortest path in a DAG has the two key properties that make it suitable for dynamic programming:
- Optimal Substructure: The shortest path from a source
sto a vertexvmust pass through one ofv's immediate predecessors. The subpath to that predecessor must itself be a shortest path. - Overlapping Subproblems: The shortest path to a vertex
umight be needed to compute the shortest paths for several other vertices thatupoints to.
Let's formalize this. We can define our DP state as:dist[v] = The length of the shortest path from the source vertex s to vertex v.
Our goal is to compute dist[v] for all v in the graph. The recurrence relation arises from the optimal substructure property. To get to v, we must have arrived from some predecessor u via the edge (u, v). The total path length would be dist[u] + weight(u, v). Since we want the shortest path, we must choose the predecessor u that minimizes this value.
This gives us the following recurrence relation:
The base case is for the source vertex s, where the distance to itself is 0: dist[s] = 0. For all other vertices v, we initialize their distances to infinity (dist[v] = ∞) to signify they are not yet reached.
Notice the striking similarity to the path-counting recurrence from the last lesson, dp[v] = Σ dp[u]. We've simply replaced summation with the min operation and incorporated the edge weights.
The following resources provide a more formal perspective on this DP formulation. The MIT video, in particular, frames this problem beautifully within a general DP framework.
15. Dynamic Programming, Part 1: SRTBOT, Fib, DAGs, Bowling
This segment from an MIT OpenCourseWare lecture on Dynamic Programming applies the DP paradigm directly to the DAG shortest paths problem.
Watch the section discussing DAG shortest paths, from the beginning. Focus on how the subproblems are defined (delta(s,v)), how the recursive relation is derived, and the crucial role of the topological order in guaranteeing a correct and efficient computation.
[PDF] Dynamic Programming 1 Overview 2 Shortest Path in a DAG
These lecture notes from Duke University provide a concise mathematical derivation of the recurrence relation.
Read the section 2 Shortest Path in a DAG. Pay close attention to how Equation (1) is refined into the well-defined recursion of Equation (2) by incorporating the constraint of a topological sort. This formalizes why the ordering is so important.
2. The Algorithm: Relaxation via Topological Sort
Having established the DP recurrence, the question remains: in what order should we compute the dist values? As you've probably guessed, a topological sort is the answer. By processing vertices in topological order, we ensure that by the time we need to calculate dist[v], we have already finalized the shortest path distances for all of its predecessors.
This allows for a simple, iterative algorithm:
- Initialize: Create a
distarray of sizeV. Setdist[source] = 0and all other entries toINF. - Topological Sort: Find a topological ordering of the graph's vertices. You can use either Kahn's algorithm or a DFS-based approach.
- Iterate and Relax: Go through each vertex
uin the computed topological order. For each outgoing edge(u, v)with weightw:- Check if we've found a shorter path to
vby going throughu. - This update step is called relaxation:
if (dist[u] + w < dist[v]), then updatedist[v] = dist[u] + w.
- Check if we've found a shorter path to
The term "relaxation" comes from the idea that we are gradually "relaxing" the distance estimates from their initial infinite values down to their true shortest path lengths.
The following resource from GeeksforGeeks outlines this process clearly.
Shortest Path in Directed Acyclic Graph - GeeksforGeeks
This article provides a great overview of the problem and the algorithm.
First, read the introduction to understand where this algorithm fits in comparison to others we will learn later. Then, study the formal algorithm description under the heading "Following is complete algorithm". This precisely matches the three steps outlined above.
3. A Visual Walkthrough
Let's trace the algorithm on an example. The image below shows a weighted DAG. The vertices are labeled r, s, t, x, y, z. Let's find the shortest paths from source s.
A valid topological sort of this graph is r, s, t, x, y, z. (Note: r comes before s because it's processed first in the traversal, but s has no incoming edges in this subgraph, so it could also come first). Let's assume our topological sort is s, t, x, y, z (ignoring r as it's unreachable from s).
-
Initialization:
distarray =[∞, 0, ∞, ∞, ∞, ∞]for(r, s, t, x, y, z). -
Topological Iteration:
- Process
s:dist[s]is0.- Relax edge
(s, t):dist[t] = min(∞, dist[s] + 2) = 2. - Relax edge
(s, x):dist[x] = min(∞, dist[s] + 6) = 6. distis now[∞, 0, 2, 6, ∞, ∞]. (See image (d))
- Relax edge
- Process
t:dist[t]is2.- Relax edge
(t, x):dist[x] = min(6, dist[t] + 7) = min(6, 2+7) = 6. No change. - Relax edge
(t, y):dist[y] = min(∞, dist[t] + 4) = 6. - Relax edge
(t, z):dist[z] = min(∞, dist[t] + 2) = 4. distis now[∞, 0, 2, 6, 6, 4]. (See image (e))
- Relax edge
- Process
x:dist[x]is6.- Relax edge
(x, y):dist[y] = min(6, dist[x] - 1) = min(6, 6-1) = 5. Note the negative weight! distis now[∞, 0, 2, 6, 5, 4]. (See image (f))
- Relax edge
- Process
y:dist[y]is5.- Relax edge
(y, z):dist[z] = min(4, dist[y] - 2) = min(4, 5-2) = 3. Again, a negative weight. distis now[∞, 0, 2, 6, 5, 3]. (See image (g))
- Relax edge
- Process
z: No outgoing edges. (Image (h) shows the final state)
- Process
Final dist array from source s: s=0, t=2, x=6, y=5, z=3. Unreachable nodes like r remain ∞.

To see this process in action with a more detailed commentary, the following video provides an excellent dry run.
G-27. Shortest Path in Directed Acyclic Graph - Topological Sort
This video from the "take U forward" channel gives a full walkthrough, from topological sort to the final distance calculations.
First, watch the topological sort dry run to refresh your memory on how the processing order is obtained. Next, watch the detailed main algorithm dry run. This is the core of the lesson, showing exactly how the dist array is updated by relaxing edges for each node in the topological order.
4. Implementation and Complexity
The C++ implementation is a direct translation of the algorithm. It involves a graph representation that supports weights (e.g., vector<pair<int, int>>), a function for topological sort, and the main loop that iterates and relaxes edges.
Shortest Path in Directed Acyclic Graph - GeeksforGeeks
This article provides a complete C++ solution.
Carefully study the C++ implementation. Notice the AdjListNode class (or std::pair in a more modern style) for storing neighbors and weights. The shortestPath function first builds the topological order in a stack using a DFS helper, then iterates through the stack to perform the relaxations. Also, pay attention to the if (dist[u] != INF) check before the inner loop—it's a crucial optimization.
Complexity Analysis
- Time Complexity: The topological sort takes time. The main loop iterates through each of the vertices once. The inner loop relaxes the outgoing edges of each vertex. Over the course of the entire algorithm, each edge is relaxed exactly once. Therefore, the relaxation phase also takes time. The total time complexity is .
- Space Complexity: Storing the graph requires space. The
distarray,visitedarray, and the stack for topological sort require an additional space. The total space complexity is .
This linear time complexity makes this algorithm exceptionally fast, outperforming general-purpose algorithms like Dijkstra's () or Bellman-Ford () on the specific case of DAGs.
Conclusion
You have now mastered a fundamental and highly efficient pattern: applying dynamic programming on a topologically sorted DAG. You've seen it work for both counting paths and, now, for finding shortest paths. The ability to recognize that a problem can be modeled as an optimization on a DAG is a key skill in competitive programming.
Key Takeaways:
- The single-source shortest path problem in a DAG can be solved using dynamic programming.
- The DP recurrence is
dist[v] = min(dist[u] + weight(u, v))over all predecessorsuofv. - A topological sort of the DAG provides the linear evaluation order needed for an efficient, iterative solution.
- The algorithm involves initializing distances, performing a topological sort, and then iterating through the sorted vertices to relax edges.
- The overall time complexity is a highly efficient .
- Crucially, this algorithm works correctly even with negative edge weights.
In the next lesson, we will make a very simple tweak to this algorithm to solve another classic problem: finding the longest path in a DAG.