Dijkstra’s Algorithm Using Python
Introduction
Dijkstra’s algorithm finds the shortest path from a starting node to all other nodes in a weighted graph. In this tutorial, we’ll break down the implementation of this algorithm into smaller, easy-to-understand code blocks.
1. Importing the Required Module
First, we need to import the heapq
module, which is used to maintain a priority queue for efficiently retrieving the node with the smallest distance.
import heapq
heapq
: This module provides an implementation of the min-heap data structure, which is crucial for efficiently finding the shortest path in Dijkstra’s algorithm.
2. Defining the Graph Class
We start by defining a Graph
class. This class will handle the graph’s vertices and edges, and will include methods for adding vertices and edges, displaying the graph, and finding the shortest path.
class Graph:
def __init__(self):
self.graph = {} # Dictionary to store the graph
__init__
: This is the constructor method for theGraph
class. It initializes an empty dictionary,self.graph
, which will be used to store the graph’s vertices and edges.
3. Adding Vertices
Next, we define a method to add vertices to the graph.
def add_vertex(self, vertex):
if vertex not in self.graph:
self.graph[vertex] = {} # Initialize with an empty dictionary for edges
add_vertex
: This method takes avertex
as input. It adds the vertex to theself.graph
dictionary with an empty dictionary as its value, which will later hold its edges and corresponding weights. Theif
statement ensures that we only add a vertex if it’s not already present.
4. Adding Edges
Now, let’s define the method for adding edges between vertices.
def add_edge(self, vertex1, vertex2, weight):
if vertex1 in self.graph and vertex2 in self.graph:
self.graph[vertex1][vertex2] = weight
self.graph[vertex2][vertex1] = weight # For undirected graph
else:
print(f"One or both vertices not found.")
add_edge
: This method takes two vertices and a weight as inputs. It adds an edge betweenvertex1
andvertex2
with the specifiedweight
. Since the graph is undirected, we add the edge in both directions. Theif
statement checks that both vertices exist in the graph.
5. Displaying the Graph
Here’s how we display the graph’s vertices and edges.
def display(self):
for vertex, edges in self.graph.items():
print(f"{vertex}: {', '.join(f'{neigh} ({wt})' for neigh, wt in edges.items())}")
display
: This method iterates over each vertex and its edges in theself.graph
dictionary. It prints the vertex and its neighbors along with the weight of the edges. Thejoin
method formats the output to make it more readable.
6. Implementing Dijkstra’s Algorithm
Now, let’s add the method to find the shortest path using Dijkstra’s algorithm.
def shortest_path(self, start, end):
# Priority queue for Dijkstra's algorithm
queue = [(0, start)]
distances = {vertex: float('inf') for vertex in self.graph}
distances[start] = 0
previous_nodes = {vertex: None for vertex in self.graph}
while queue:
current_distance, current_vertex = heapq.heappop(queue)
if current_distance > distances[current_vertex]:
continue
for neighbor, weight in self.graph[current_vertex].items():
distance = current_distance + weight
if distance < distances[neighbor]:
distances[neighbor] = distance
previous_nodes[neighbor] = current_vertex
heapq.heappush(queue, (distance, neighbor))
# Reconstruct the shortest path
path = []
while end is not None:
path.append(end)
end = previous_nodes[end]
return path[::-1], distances[path[0]]
shortest_path
: This method finds the shortest path from thestart
vertex to theend
vertex using Dijkstra’s algorithm.- Priority Queue Initialization: The queue starts with a tuple
(0, start)
, where0
is the initial distance andstart
is the starting vertex. - Distance Initialization:
distances
dictionary keeps track of the shortest known distance to each vertex. Initially, all distances are set to infinity except for thestart
vertex, which is set to0
. - Previous Nodes:
previous_nodes
keeps track of the best previous vertex for each vertex to reconstruct the path. - Processing Queue: The algorithm extracts the vertex with the smallest distance from the priority queue, updates the distances to its neighbors, and pushes these neighbors into the queue if a shorter path is found.
- Path Reconstruction: After processing, the path is reconstructed by tracing back from the
end
vertex to thestart
vertex usingprevious_nodes
.
7. Example Usage
Finally, here’s how to use the Graph
class to create a graph, add vertices and edges, and find the shortest path.
# Example usage
if __name__ == "__main__":
# Create a graph instance
g = Graph()
# Adding vertices
g.add_vertex('A')
g.add_vertex('B')
g.add_vertex('C')
g.add_vertex('D')
# Adding edges with weights
g.add_edge('A', 'B', 1)
g.add_edge('A', 'C', 4)
g.add_edge('B', 'C', 2)
g.add_edge('C', 'D', 1)
# Display the graph
print("Graph:")
g.display()
# Find shortest path from A to D
path, distance = g.shortest_path('A', 'D')
print(f"\nShortest path from A to D: {' -> '.join(path)} with distance {distance}")
- Creating the Graph: An instance of the
Graph
class is created. - Adding Vertices and Edges: Vertices and weighted edges are added to the graph.
- Displaying the Graph: The graph’s structure is printed.
- Finding the Shortest Path: The shortest path from vertex
A
to vertexD
is calculated and displayed.
Conclusion
By breaking down Dijkstra’s algorithm into these manageable code blocks, you can see how each part contributes to finding the shortest path in a graph. This implementation provides a clear and practical way to understand and apply Dijkstra’s algorithm in Python.