Report Content
×Shortest Path Algorithms: Complete Guide
Master shortest path finding in graphs with Dijkstra's, Bellman-Ford, Floyd-Warshall, and A* algorithms. Learn when to use each method with complexity analysis and real-world applications.
Algorithm Usage in Industry (2023)
1. Dijkstra's Algorithm
Dijkstra's Shortest Path
Definition
Dijkstra's Algorithm finds the shortest paths from a source node to all other nodes in a weighted graph with non-negative edge weights. It uses a greedy approach with a priority queue to always explore the closest unvisited vertex.
How It Works
Initialize distances
- Set distance to source = 0, all others = ∞
- Add all vertices to priority queue with their distances
Extract minimum vertex
- Remove vertex with smallest distance from queue
- If distance is outdated, skip
Relax edges
- For each neighbor, calculate new distance = current + edge weight
- If new distance < known distance, update and re-add to queue
Repeat
- Continue until queue is empty
- All shortest paths are found
import heapq
def dijkstra(graph, start):
distances = {node: float('inf') for node in graph}
distances[start] = 0
pq = [(0, start)]
predecessors = {node: None for node in graph}
while pq:
current_dist, u = heapq.heappop(pq)
if current_dist > distances[u]:
continue
for v, weight in graph[u]:
distance = current_dist + weight
if distance < distances[v]:
distances[v] = distance
predecessors[v] = u
heapq.heappush(pq, (distance, v))
return distances, predecessors
# Get shortest path from start to target
def get_path(predecessors, target):
path = []
while target is not None:
path.append(target)
target = predecessors[target]
return path[::-1]
Complexity: O((V+E) log V) with binary heap, O(V²) with array
Constraints: No negative edge weights
Use When: Single-source shortest paths in non-negative graphs
2. Bellman-Ford Algorithm
Bellman-Ford Shortest Path
Definition
Bellman-Ford Algorithm computes shortest paths from a source vertex to all other vertices in a weighted graph, even when negative edge weights exist. It can also detect negative weight cycles.
How It Works
Initialize distances
- Set distance to source = 0, all others = ∞
- Store edges in list for easy iteration
Relax edges repeatedly
- Repeat V-1 times: iterate through all edges
- If dist[u] + weight < dist[v], update dist[v]
Check for negative cycles
- Run one more relaxation pass
- If any distance improves, negative cycle exists
def bellman_ford(vertices, edges, start):
distances = {v: float('inf') for v in vertices}
distances[start] = 0
predecessors = {v: None for v in vertices}
# Relax edges V-1 times
for _ in range(len(vertices) - 1):
for u, v, weight in edges:
if distances[u] != float('inf') and distances[u] + weight < distances[v]:
distances[v] = distances[u] + weight
predecessors[v] = u
# Check for negative cycles
for u, v, weight in edges:
if distances[u] != float('inf') and distances[u] + weight < distances[v]:
raise ValueError("Graph contains negative weight cycle")
return distances, predecessors
# Example usage
vertices = ['A', 'B', 'C', 'D']
edges = [
('A', 'B', 4), ('A', 'C', 2),
('B', 'C', 1), ('B', 'D', 5),
('C', 'D', 8)
]
dist, pred = bellman_ford(vertices, edges, 'A')
Complexity: O(V×E) time, O(V) space
Advantage: Handles negative edges, detects negative cycles
Use When: Graphs with negative edge weights
3. Floyd-Warshall Algorithm
Floyd-Warshall (All-Pairs Shortest Path)
Definition
Floyd-Warshall Algorithm finds shortest paths between all pairs of vertices in a weighted graph. It uses dynamic programming to consider each vertex as a potential intermediate point.
How It Works
Initialize distance matrix
- dist[i][j] = 0 if i == j
- dist[i][j] = weight(i,j) if edge exists
- dist[i][j] = ∞ if no direct edge
Consider each vertex as intermediate
- For k from 0 to V-1:
- For i from 0 to V-1, j from 0 to V-1:
Update using dynamic programming
- If dist[i][k] + dist[k][j] < dist[i][j]:
- Update dist[i][j] = dist[i][k] + dist[k][j]
def floyd_warshall(graph, n):
# Initialize distance matrix
dist = [[float('inf')] * n for _ in range(n)]
next_node = [[None] * n for _ in range(n)]
for i in range(n):
dist[i][i] = 0
next_node[i][i] = i
for u, v, w in graph:
dist[u][v] = w
next_node[u][v] = v
# Dynamic programming
for k in range(n):
for i in range(n):
for j in range(n):
if dist[i][k] + dist[k][j] < dist[i][j]:
dist[i][j] = dist[i][k] + dist[k][j]
next_node[i][j] = next_node[i][k]
# Check for negative cycles
for i in range(n):
if dist[i][i] < 0:
raise ValueError("Graph contains negative cycle")
return dist, next_node
# Reconstruct path
def get_path(next_node, i, j):
if next_node[i][j] is None:
return []
path = [i]
while i != j:
i = next_node[i][j]
path.append(i)
return path
Complexity: O(V³) time, O(V²) space
Advantage: Simpler implementation, finds all-pairs
Use When: Dense graphs or when all-pairs needed
4. A* Search Algorithm
A* (A-Star) Pathfinding
Definition
A* Search is an informed graph traversal algorithm that finds the shortest path using a heuristic to guide its search. It combines Dijkstra's algorithm (uniform cost) with Greedy Best-First Search (heuristic).
How It Works
Initialize scores
- gScore: actual cost from start to node
- fScore = gScore + heuristic(node, goal)
- Priority queue ordered by fScore
Extract best candidate
- Pop node with smallest fScore
- If node == goal, reconstruct path
Explore neighbors
- Calculate tentative gScore
- If better than known, update scores
- Push neighbors to priority queue
Heuristic must be admissible
- Never overestimates true cost
- Ensures optimal path (consistent heuristic)
import heapq
def a_star(start, goal, get_neighbors, heuristic):
open_set = [(0, start)]
came_from = {}
g_score = {start: 0}
f_score = {start: heuristic(start, goal)}
while open_set:
current_f, current = heapq.heappop(open_set)
if current == goal:
return reconstruct_path(came_from, current)
for neighbor in get_neighbors(current):
tentative_g = g_score[current] + get_edge_weight(current, neighbor)
if tentative_g < g_score.get(neighbor, float('inf')):
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score[neighbor] = tentative_g + heuristic(neighbor, goal)
heapq.heappush(open_set, (f_score[neighbor], neighbor))
return None # No path found
def reconstruct_path(came_from, current):
path = [current]
while current in came_from:
current = came_from[current]
path.append(current)
return path[::-1]
# Example: Manhattan heuristic for grid
def manhattan_heuristic(a, b):
return abs(a[0] - b[0]) + abs(a[1] - b[1])
Complexity: O(E) with good heuristic, worst-case O(b^d)
Heuristic: Must be admissible (never overestimate)
Use When: Pathfinding with domain knowledge (navigation, games)
5. Algorithm Comparison & Selection
Algorithm Properties Comparison
| Algorithm | Graph Type | Negative Edges | Negative Cycles | Optimal For |
|---|---|---|---|---|
| Dijkstra's | Weighted | No | No | Single-source |
| Bellman-Ford | Weighted | Yes | Detects | Single-source |
| Floyd-Warshall | Weighted | Yes | Detects | All-pairs |
| A* | Weighted | No | No | Single-pair |
6. Real-World Applications
GPS Navigation Systems
Dijkstra/A*: Find fastest route between locations
def gps_navigation(graph, start, destination):
# Use A* with Euclidean distance heuristic
def heuristic(a, b):
return haversine_distance(a, b)
return a_star(start, destination, graph, heuristic)
Network Routing (OSPF)
Dijkstra's: Open Shortest Path First protocol
def ospf_routing_table(network_topology, router_id):
distances, _ = dijkstra(network_topology, router_id)
return distances
Financial Arbitrage
Bellman-Ford: Detect negative cycles in currency exchange
def detect_arbitrage(exchange_rates):
# Convert to log negative weights
graph = convert_to_graph(exchange_rates)
try:
distances, _ = bellman_ford(graph, 0)
return True # No arbitrage
except ValueError:
return False # Arbitrage opportunity found
Game AI Pathfinding
A*: NPC movement with obstacle avoidance
def game_ai_path(start, goal, obstacles):
def heuristic(a, b):
return abs(a[0]-b[0]) + abs(a[1]-b[1])
return a_star(start, goal, get_neighbors(obstacles), heuristic)
Algorithm Selection Guide
| Scenario | Recommended Algorithm | Reason |
|---|---|---|
| Road networks (non-negative weights) | Dijkstra's with Priority Queue | Most efficient for single-source |
| Financial arbitrage detection | Bellman-Ford | Detects negative cycles |
| Small dense graphs (all pairs) | Floyd-Warshall | Precomputes all paths |
| Game pathfinding with obstacles | A* with Manhattan heuristic | Intelligently explores towards goal |
| Robotics with unknown environment | Dijkstra's (no heuristic) | Guaranteed optimal path |
Shortest Path Implementation Checklist
Pro Tip: For large-scale road networks, combine Dijkstra's with Contraction Hierarchies or ALT heuristics to achieve sub-second query times on continent-scale graphs. For real-time applications, consider bidirectional search which can reduce search space by up to 50%.
You need to be logged in to participate in this discussion.