Part II · Shortest paths · week 4
Shortest paths in a DAG
In an acyclic digraph, relax vertices in topological order and one pass is enough. Linear time, no priority queue, and negative weights are welcome.
Run the animation, step by step → generated live from any input you type — nothing is pre-recorded
Cost and properties
| time | O(V + E) — linear |
|---|---|
| requires | no directed cycle |
| negative weights | fine |
| also gives | longest paths (negate) |
Reference: Sedgewick & Wayne, §4.4.
Shortest paths in a DAG in Python
Runnable as-is, and written for reading. Watch the highlighted line move through it in the animation, or send it straight to the visualizer — it arrives with a test case ready to run, yours to edit.
class AcyclicSP:
# Relax vertices in topological order. When you reach v, every edge
# that could possibly improve dist_to[v] has already been relaxed,
# so dist_to[v] is final -- no priority queue needed, and NEGATIVE
# weights are no problem at all.
def __init__(self, digraph, s):
self.dist_to = [float('inf')] * digraph.V
self.edge_to = [None] * digraph.V
self.dist_to[s] = 0.0
for v in Topological(digraph).order:
for w, weight in digraph.adj(v):
if self.dist_to[w] > self.dist_to[v] + weight:
self.dist_to[w] = self.dist_to[v] + weight
self.edge_to[w] = v
Why it works
Why one pass suffices
Topological order guarantees every edge points forwards. So by the time the loop reaches v, every edge u→v has already been relaxed — there is no route left that could improve dist_to[v]. Each edge is relaxed exactly once and the whole computation is linear, beating Dijkstra's O(E log V) with no data structure more advanced than a list.
The general lesson: if you can find an order in which subproblems are already solved, you do not need a priority queue. That is the same insight as dynamic programming.
Negative weights are fine here
The correctness argument never mentions the sign of the weights — only the order. So this algorithm handles negative edges that would silently break Dijkstra. And since a DAG has no cycles, there can be no negative cycle to make “shortest” meaningless.
Longest paths for free
Negate every weight (or flip the comparison) and the same pass computes longest paths — which is the critical path method for scheduling: the longest path through a task DAG is the earliest possible finish time, and it tells you which tasks are on the critical path. Longest paths in a general digraph are NP-hard; in a DAG they are linear. The acyclicity is doing enormous work.
Also used for
Seam carving (a pixel DAG), content-aware resizing, parallel job scheduling with precedence constraints, and DP on DAGs generally — including the shortest-path formulation of many dynamic-programming problems.
What to try in the animation
The default is tinyEWDAG.txt from the course. Try a negative weight — Dijkstra would get it wrong, this will not.
The animation is generated from the input box, in your browser — press Animate after editing it. To execute the Python itself, use Open in Visualizer above.
- tinyEWDAG (course) —
vertices: 8 · edges: 5-4:0.35 4-7:0.37 5-7:0.28 5-1:0.32 4-0:0.38 0-2:0.26 3-7:0.39 1-3:0.29 7-2:0.34 6-2:0.40 3-6:0.52 6-0:0.58 6-4:0.93 · source: 5 - with a negative weight —
vertices: 6 · edges: 0-1:5 0-2:3 1-3:6 2-1:2 2-3:7 2-4:4 3-4:-1 3-5:1 4-5:-2 · source: 0 - a simple chain —
vertices: 5 · edges: 0-1:2 1-2:3 2-3:1 3-4:4 0-2:9 · source: 0
The rest of Shortest paths
- Dijkstra's algorithm Repeatedly settle the unsettled vertex closest to the source and relax its edges.
- Bellman–Ford Relax every edge, V−1 times. Slower than Dijkstra, but it handles negative weights and…