Dijkstra's, Bellman-Ford, and Floyd-Warshall Algorithms: Key Differences and Python Implementations
Dijkstra's algorithm excels for single-source shortest paths with non-negative weights in sparse graphs, Bellman-Ford handles negative edges and detects negative cycles for single-source problems, and Floyd-Warshall computes all-pairs shortest paths using dynamic programming for dense graphs.
Choosing the right shortest-path algorithm depends on your graph's edge weights, density, and whether you need distances from one source or between every pair of vertices. The TheAlgorithms/Python repository provides clean, educational implementations of all three algorithms in the graphs/ directory, making it easy to compare their practical differences.
Algorithm Overview and Key Characteristics
Dijkstra's Algorithm: Single-Source with Non-Negative Weights
Dijkstra's algorithm solves the single-source shortest path problem for graphs with non-negative edge weights. As implemented in graphs/dijkstra_algorithm.py, the Graph class uses a custom PriorityQueue (min-heap) to greedily select the unvisited node with the smallest tentative distance.
The algorithm guarantees optimal results because once a vertex is extracted from the heap, its shortest distance is finalized. However, negative edges break this guarantee—Dijkstra's may produce incorrect results if negative weights exist because it never revisits finalized nodes.
Bellman-Ford Algorithm: Handling Negative Edges and Cycle Detection
Bellman-Ford also solves the single-source shortest path problem but relaxes the non-negative constraint. The implementation in graphs/bellman_ford.py accepts graphs with negative edge weights and explicitly detects negative-weight cycles that would make shortest paths undefined.
The algorithm works by relaxing all edges V-1 times (where V is the vertex count), followed by an additional pass to check for negative cycles. If any distance can still be reduced, a negative cycle exists and the function raises an exception. This robustness comes at a higher computational cost than Dijkstra's.
Floyd-Warshall Algorithm: All-Pairs Shortest Paths
Floyd-Warshall takes a fundamentally different approach, solving the all-pairs shortest path problem using dynamic programming. The implementation in graphs/graphs_floyd_warshall.py computes shortest distances between every pair of vertices simultaneously.
The algorithm iteratively improves shortest paths by considering each vertex as a potential intermediate point. It handles negative edge weights but does not detect negative cycles—if one exists, the results will be incorrect without warning. Floyd-Warshall is ideal when you need a complete distance matrix for dense graphs or frequent path queries between arbitrary nodes.
Time and Space Complexity Comparison
Understanding the computational trade-offs helps select the right algorithm for your data scale:
| Algorithm | Time Complexity | Space Complexity | Best For |
|---|---|---|---|
| Dijkstra (min-heap) | O((V + E) log V) |
O(V + E) |
Large sparse graphs with non-negative weights |
| Bellman-Ford | O(V · E) |
O(V + E) |
Graphs with negative edges; cycle detection required |
| Floyd-Warshall | O(V³) |
O(V²) |
Dense graphs; all-pairs distance matrix needed |
Dijkstra's logarithmic factor makes it the fastest for sparse graphs like road networks. Bellman-Ford's linear dependency on edges suits smaller graphs where negative weights exist. Floyd-Warshall's cubic time is acceptable only for dense graphs or when you need constant-time lookups between any two nodes after preprocessing.
When to Use Each Algorithm
Use Dijkstra's Algorithm When:
- All edge weights are non-negative (road distances, time costs)
- You need shortest paths from one source to all other nodes
- Working with large, sparse graphs where
E << V² - Real-time performance is critical (GPS navigation, game pathfinding)
Use Bellman-Ford Algorithm When:
- Edge weights may be negative (financial arbitrage, profit/loss scenarios)
- You need to detect negative-weight cycles that invalidate shortest paths
- The graph is small to moderate in size (highway tolls with discounts)
- You only need single-source results but require robustness against negative data
Use Floyd-Warshall Algorithm When:
- You need shortest paths between all pairs of vertices (network latency tables)
- The graph is dense or moderately sized (typically
V < 500-1000) - You want constant-time queries after preprocessing (dynamic programming table lookup)
- Transitive closure or reachability analysis is required (path existence, not just distance)
Implementation Examples from TheAlgorithms/Python
Dijkstra's Implementation
The graphs/dijkstra_algorithm.py file implements Dijkstra's using an adjacency list and custom PriorityQueue:
from graphs.dijkstra_algorithm import Graph
# Create graph with 5 vertices
g = Graph(5)
g.add_edge(0, 1, 4)
g.add_edge(0, 2, 1)
g.add_edge(1, 3, 1)
g.add_edge(2, 1, 2)
g.add_edge(2, 3, 5)
g.add_edge(3, 4, 3)
# Compute shortest paths from source 0
g.dijkstra(src=0)
g.show_path(0, 4) # Output: 0 -> 2 -> 1 -> 3 -> 4
The dijkstra() method uses a min-heap to achieve O((V+E) log V) complexity, finalizing each node's distance upon extraction from the priority queue.
Bellman-Ford Implementation
The graphs/bellman_ford.py file provides a function that handles negative edges and detects cycles:
from graphs.bellman_ford import bellman_ford
# Edge list representation with a negative weight
edges = [
{"src": 0, "dst": 1, "weight": 5},
{"src": 1, "dst": 2, "weight": -2},
{"src": 2, "dst": 3, "weight": 3},
{"src": 3, "dst": 1, "weight": 1} # Creates cycle with positive weight
]
# Compute distances from source 0
distances = bellman_ford(edges, vertex_count=4, edge_count=4, src=0)
print(distances) # -> [0.0, 5.0, 3.0, 6.0]
The implementation relaxes all edges V-1 times, then performs an additional pass to check for negative cycles, raising an exception if detected.
Floyd-Warshall Implementation
The graphs/graphs_floyd_warshall.py file implements the dynamic programming approach for all-pairs shortest paths:
from graphs.graphs_floyd_warshall import floyd_warshall
INF = float('inf')
# Adjacency matrix representation
graph = [
[0, 3, INF, 7],
[8, 0, 2, INF],
[5, INF, 0, 1],
[2, INF, INF, 0],
]
dist_matrix, _ = floyd_warshall(graph, v=4)
# Result: shortest distance between every pair of vertices
print(dist_matrix[0][3]) # -> 6 (path: 0->1->2->3 or 0->3 directly is 7, but 0->1->2->3 = 3+2+1=6)
This implementation uses three nested loops to iteratively improve paths through intermediate vertices, returning a complete distance matrix suitable for constant-time lookups.
Summary
-
Dijkstra's algorithm (
graphs/dijkstra_algorithm.py) provides the fastest single-source solution for graphs with non-negative weights, using a min-heap priority queue to achieveO((V+E) log V)time complexity. -
Bellman-Ford algorithm (
graphs/bellman_ford.py) handles negative edge weights and detects negative-weight cycles throughV-1relaxation passes plus a validation step, making it essential for financial modeling and arbitrage detection despite itsO(V·E)complexity. -
Floyd-Warshall algorithm (
graphs/graphs_floyd_warshall.py) computes all-pairs shortest paths using dynamic programming inO(V³)time andO(V²)space, ideal for dense networks requiring constant-time distance queries between arbitrary vertices.
Choose Dijkstra's for speed on large sparse graphs with positive weights, Bellman-Ford when negative edges or cycle detection is required, and Floyd-Warshall when you need a complete distance matrix for all vertex pairs.
Frequently Asked Questions
Can Dijkstra's algorithm handle negative edge weights?
No, Dijkstra's algorithm cannot handle negative edge weights. The algorithm assumes that once a vertex is extracted from the priority queue, its shortest distance is finalized. Negative edges can violate this assumption by providing a shorter path to an already-finalized vertex through a different route. For graphs with negative weights, use the Bellman-Ford algorithm implemented in graphs/bellman_ford.py.
How does Bellman-Ford detect negative cycles?
Bellman-Ford detects negative cycles by performing one additional relaxation pass after the standard V-1 iterations. If any distance can be reduced during this extra pass, a negative-weight cycle exists that can be traversed indefinitely to decrease path cost. The implementation in graphs/bellman_ford.py raises an exception when such a cycle is detected, preventing the return of invalid shortest paths.
When should I choose Floyd-Warshall over running Dijkstra or Bellman-Ford multiple times?
Choose Floyd-Warshall when you need shortest paths between all pairs of vertices and the graph is dense or moderately sized (typically fewer than 500-1000 vertices). Running Dijkstra's algorithm from every source would cost O(V·(V+E) log V), which exceeds Floyd-Warshall's O(V³) when E approaches V². Additionally, Floyd-Warshall handles negative edges (without cycle detection) and provides constant-time O(1) distance lookups after the initial computation, making it ideal for network latency tables and transitive closure operations.
What is the space complexity difference between these three algorithms?
Dijkstra's and Bellman-Ford both require O(V + E) space, storing adjacency lists and distance arrays. Dijkstra's adds O(V) for the priority queue, while Bellman-Ford stores the edge list explicitly. Floyd-Warshall requires O(V²) space to store the distance matrix for all pairs, making it more memory-intensive for large graphs but necessary for all-pairs queries.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →