Shortest Path Algorithms & Transitive Closure
Navigate the core algorithms for finding shortest paths in a graph. Learn when to use Dijkstra's Algorithm vs Bellman-Ford based on edge weights, and how to compute All-Pairs Shortest Paths using Floyd-Warshall.
Learning Goals
- Answer system design questions by choosing the correct shortest-path algorithm based on edge weight constraints.
- Perform a step-by-step trace of Dijkstra's algorithm using the edge relaxation method.
- Trace Bellman-Ford's algorithm and explain how it detects negative weight cycles.
- Understand the DP recurrence relation for the Floyd-Warshall algorithm.
System Design: Shortest Paths on Google Maps
When designing a routing system like Google Maps, the choice of the shortest path algorithm depends entirely on the nature of the graph's edge weights (road lengths/traffic delays).
-
All roads have equal length (Unweighted Graph):
- Algorithm: Breadth-First Search (BFS).
- Reasoning: BFS explores layer-by-layer. The first time it reaches the destination, it guarantees the minimum number of edges. It is much faster () than applying a weighted algorithm.
-
Roads have varying lengths, but NO negative lengths:
- Algorithm: Dijkstra's Algorithm.
- Reasoning: Dijkstra uses a priority queue (Min-Heap) to greedily lock in the shortest path to a node. Since there are no negative weights, once a node's distance is locked, it can never be improved. Time complexity is .
-
Some roads have negative lengths (Negative weight cycles):
- Algorithm: Bellman-Ford Algorithm.
- Reasoning: Dijkstra fails if negative weights exist because it might lock a node's distance prematurely. Bellman-Ford systematically relaxes all edges times, guaranteeing it finds the shortest path even with negative edges. It also runs a final -th pass to explicitly detect "negative weight cycles" (where looping infinitely reduces the cost). Time complexity is .
Dijkstra's Algorithm Pseudocode
(Answers 2024 Q6b)
1Algorithm Dijkstra(G, source): 2 For each vertex v in G: 3 Dist[v] = INFINITY 4 Prev[v] = UNDEFINED 5 Dist[source] = 0 6 7 Let PQ be a Priority Queue (Min-Heap) 8 PQ.insert(source, 0) 9 10 While PQ is not empty: 11 u = PQ.extract_min() 12 13 For each neighbor v of u: 14 alt_dist = Dist[u] + weight(u, v) 15 If alt_dist < Dist[v]: 16 Dist[v] = alt_dist 17 Prev[v] = u 18 PQ.insert(v, alt_dist)
Trace: Dijkstra's Algorithm (Single Source Shortest Path)
- 1Step 1
- 2Step 2
We want to find the shortest path from Source A to all other nodes.
- Initialize distances:
Dist[A] = 0, all others to . - Priority Queue (Min-Heap):
[(0, A)] - Visited Set:
{}
- Initialize distances:
- 3Step 3
Extract A from PQ. Mark A as visited. Look at neighbors of A: B (weight 4) and C (weight 2).
Dist[A] + 4 < Dist[B]. UpdateDist[B] = 4.Dist[A] + 2 < Dist[C]. UpdateDist[C] = 2. Push to PQ:[(2, C), (4, B)]
- 4Step 4
Extract C (minimum distance = 2) from PQ. Mark C visited. Neighbors of C: B(1), D(8), E(10).
- Path to B via C:
Dist[C] + 1 = 2 + 1 = 3. Since , updateDist[B] = 3. - Path to D via C:
Dist[C] + 8 = 2 + 8 = 10. UpdateDist[D] = 10. - Path to E via C:
Dist[C] + 10 = 2 + 10 = 12. UpdateDist[E] = 12. Push updates to PQ. PQ extracts B next (distance = 3).
- Path to B via C:
- 5Step 5
Extract B (distance = 3) from PQ. Mark B visited. Neighbors of B: D(5).
- Path to D via B:
Dist[B] + 5 = 3 + 5 = 8. Since , updateDist[D] = 8. PQ extracts D next (distance = 8).
- Path to D via B:
- 6Step 6
Extract D (distance = 8). Neighbors: E(2).
- Path to E via D:
Dist[D] + 2 = 8 + 2 = 10. Since , updateDist[E] = 10. Extract E. No outgoing neighbors. Final Distances from A: B=3, C=2, D=8, E=10.
- Path to E via D:
Trace: Bellman-Ford on a Matrix
- 1Step 1
Find shortest distance from Source Node 3.
1 1 2 3 4 5 21 0 1 8 1 4 32 1 0 12 4 9 43 8 12 0 7 3 54 1 4 7 0 2 65 4 9 3 2 0Because the matrix is symmetric, it is an undirected graph. We extract all edges and their weights.
- 2Step 2
Set Dist[3] = 0. All other nodes to .
Dist = [\infty, \infty, 0, \infty, \infty]We have , so we will run relaxation passes over all edges. - 3Step 3
We relax all edges. Edges leaving Node 3 will update first because
Dist[3]is 0.Dist[3] + weight(3,1) < Dist[1]. UpdateDist[1] = 8.- Update
Dist[2] = 12. - Update
Dist[4] = 7. - Update
Dist[5] = 3. After Pass 1:Dist = [8, 12, 0, 7, 3].
- 4Step 4
Now we relax all edges again using the updated distances.
- Edge (5,4) weight is 2.
Dist[5] + 2 < Dist[4]. UpdateDist[4] = 5. - Edge (4,1) weight is 1.
Dist[4] + 1 < Dist[1]. UpdateDist[1] = 6. - Edge (5,1) weight is 4.
Dist[5] + 4 < Dist[1](No update, 6 is smaller). - Edge (4,2) weight is 4.
Dist[4] + 4 < Dist[2]. UpdateDist[2] = 9. After Pass 2:Dist = [6, 9, 0, 5, 3].
- Edge (5,4) weight is 2.
- 5Step 5
We relax all edges again in Pass 3 and Pass 4. If we check (4,1),
5 + 1 = 6, no change. If we check (4,2),5 + 4 = 9, no change. No distances are updated in Pass 3 or Pass 4. Since no updates occurred, the algorithm can terminate early! Final Shortest Distances from Node 3: Node 1: 6, Node 2: 9, Node 4: 5, Node 5: 3.
All-Pairs Shortest Path: Floyd-Warshall Algorithm
What if we want the shortest path between every possible pair of nodes, not just from a single source? We use the Floyd-Warshall algorithm. It is a Dynamic Programming algorithm that utilizes an Adjacency Matrix.
The DP Logic:
For every pair of nodes i and j, we check if going through an intermediate node k is shorter than the direct path.
1For k = 1 to V: 2 For i = 1 to V: 3 For j = 1 to V: 4 Dist[i][j] = MIN( Dist[i][j] , Dist[i][k] + Dist[k][j] )
Because of the 3 nested loops, the time complexity is strictly .
Transitive Closure (Warshall's Algorithm)
Transitive Closure asks a simpler question: Is there any path (direct or indirect) from node i to node j?
Instead of tracking weights, the matrix only stores (Path Exists) or (No Path). The logic is identical to Floyd-Warshall, but uses Boolean logic (AND / OR) instead of addition and MIN.
Knowledge Check
Which of the following algorithm solves the All Pair Shortest Path problem?