Shortest Path in DAG Using Topological Order in DSA Go - Time & Space Complexity
We want to understand how the time needed to find the shortest path in a Directed Acyclic Graph (DAG) grows as the graph gets bigger.
Specifically, how does the algorithm using topological order handle larger graphs?
Analyze the time complexity of the following code snippet.
func shortestPathDAG(graph [][]Edge, start int) []int {
n := len(graph)
dist := make([]int, n)
for i := range dist {
dist[i] = math.MaxInt32
}
dist[start] = 0
order := topologicalSort(graph)
for _, u := range order {
if dist[u] != math.MaxInt32 {
for _, e := range graph[u] {
if dist[u]+e.Weight < dist[e.To] {
dist[e.To] = dist[u] + e.Weight
}
}
}
}
return dist
}
This code finds shortest paths from a start node to all others in a DAG using topological sorting and edge relaxation.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The nested loops: outer loop over all nodes in topological order, inner loop over edges from each node.
- How many times: Outer loop runs once per node (n times), inner loop runs once per edge (total m times across all nodes).
As the graph grows, the algorithm visits each node once and checks all edges once.
| Input Size (n nodes, m edges) | Approx. Operations |
|---|---|
| 10 nodes, 15 edges | ~25 operations (10 + 15) |
| 100 nodes, 200 edges | ~300 operations (100 + 200) |
| 1000 nodes, 5000 edges | ~6000 operations (1000 + 5000) |
Pattern observation: The total work grows roughly in proportion to the sum of nodes and edges.
Time Complexity: O(n + m)
This means the time grows linearly with the number of nodes plus the number of edges in the graph.
[X] Wrong: "The algorithm checks all paths, so it must be exponential in time."
[OK] Correct: Because the graph is acyclic and we use topological order, each edge and node is processed only once, avoiding repeated work.
Understanding this time complexity shows you can analyze graph algorithms that efficiently handle special cases like DAGs, a valuable skill in many coding challenges.
"What if the graph had cycles? How would the time complexity and approach change?"
Practice
Solution
Step 1: Understand topological order in DAG
Topological order arranges vertices so that all edges go from earlier to later vertices, ensuring no cycles.Step 2: Use topological order for shortest path
Processing vertices in this order means each vertex is processed after all its predecessors, so shortest paths can be updated without revisiting.Final Answer:
It allows processing vertices in a linear order without revisiting nodes. -> Option DQuick Check:
Topological order = linear processing without revisits [OK]
- Thinking topological order applies to graphs with cycles
- Confusing topological order with sorting by weights
- Assuming it finds longest path by default
Solution
Step 1: Initialize distances with max int
We set all distances to a large number (infinity) to represent unknown paths, here 1<<31 - 1 for max int.Step 2: Set source distance to zero
The starting vertex distance is zero because the shortest path from source to itself is zero.Final Answer:
dist := make([]int, 5); for i := range dist { dist[i] = 1<<31 - 1 }; dist[0] = 0 -> Option AQuick Check:
Initialize with max int, source zero [OK]
- Setting all distances to zero initially
- Not setting source distance to zero
- Using -1 as initial distance
dist := []int{0, 2147483647, 2147483647, 2147483647}
// After processing edges in topological orderSolution
Step 1: Relax edges from source 0
dist[1] = min(inf, 0+2) = 2; dist[2] = min(inf, 0+4) = 4Step 2: Relax edges from vertex 1 and 2
From 1: dist[2] = min(4, 2+1) = 3; dist[3] = min(inf, 2+7) = 9
From 2: dist[3] = min(9, 3+3) = 6Final Answer:
[0, 2, 3, 6] -> Option CQuick Check:
Relax edges in topological order = [0,2,3,6] [OK]
- Not updating dist[2] to 3 after relaxing from vertex 1
- Ignoring relaxation from vertex 2 to 3
- Using initial distances without updates
dist := make([]int, 4)
for i := range dist {
dist[i] = 1<<31 - 1
}
dist[0] = 0
for _, u := range topoOrder {
for _, edge := range graph[u] {
if dist[u]+edge.weight < dist[edge.to] {
dist[edge.to] = dist[u] + edge.weight
}
}
}
If the graph contains a cycle, what problem will occur?Solution
Step 1: Understand topological order requirement
Topological order exists only if the graph has no cycles. If a cycle exists, topoOrder is invalid or partial.Step 2: Effect of cycle on code execution
Standard topological sort algorithms produce a partial order (not all vertices included), so the loop will skip some vertices but finish normally since topoOrder is a finite slice.Final Answer:
The code will skip some vertices but finish normally. -> Option BQuick Check:
Cycle causes partial topoOrder, skips vertices [OK]
- Assuming code works fine with cycles
- Expecting panic instead of infinite loop
- Ignoring the need for cycle detection
Solution
Step 1: Initialize distances
dist = [0, inf, inf, inf, inf, inf]Step 2: Relax edges in topological order
From 0: dist[1]=5, dist[2]=3
From 1: dist[3]=min(inf,5+6=11)=11, dist[2]=min(3,5+2=7)=3
From 2: dist[4]=min(inf,3+4=7)=7, dist[5]=min(inf,3+2=5)=5, dist[3]=min(11,3+7=10)=10
From 3: dist[4]=min(7,10+(-1)=9)=7
From 4: dist[5]=min(5,7+(-2)=5)=5Step 3: Check final distance to vertex 5
dist[5] = 5Final Answer:
5 -> Option AQuick Check:
Relax edges carefully to get dist[5] = 5 [OK]
- Ignoring negative edge weights
- Not updating distances after relaxation
- Assuming negative weights cause cycles
Ready to prove it?
Pass the test and this pattern moves unknown
