Skip to content

Implementing Dijkstra's Algorithm in Python: A Step-by-Step Guide

Updated: at 03:12 AM

Dijkstra’s algorithm is a graph search algorithm that solves the single-source shortest path problem for a graph with non-negative edge path costs. This means given a graph G=(V,E) with vertex set V, edge set E, and a start vertex s, Dijkstra’s algorithm finds the shortest path from s to every other vertex in the graph. It was conceived by computer scientist Edsger W. Dijkstra in 1956 and is a fundamental algorithm used in routing, pathfinding, and graph analysis.

This comprehensive guide will explain Dijkstra’s algorithm and provide step-by-step instructions on how to implement it in Python. We will cover the following topics:

Table of Contents

Open Table of Contents

Overview of Dijkstra’s Algorithm

Dijkstra’s algorithm, named after its creator Dutch computer scientist Edsger W. Dijkstra, is a graph search algorithm that solves the single-source shortest path problem for a graph with non-negative edge path costs. For a given source vertex in the graph, Dijkstra’s algorithm finds the shortest path between that vertex and every other vertex. It can also be used to find the shortest paths from a single vertex to a single destination vertex by stopping the algorithm once the shortest path to the destination vertex has been determined.

The algorithm works by assigning a tentative distance value to each vertex in the graph, initialized to infinity. Distances are iteratively updated for each vertex as closer paths are found, eventually converging on the true shortest path distances. To track the path, each vertex keeps track of the neighboring vertex that is its current shortest path to the source.

Some key properties of Dijkstra’s algorithm:

Applications of Dijkstra’s Algorithm

Dijkstra’s algorithm has many applications in fields like network routing protocols, transportation, robotics, and video games due to its ubiquity as the shortest path algorithm. Here are some examples of applying Dijkstra’s algorithm:

These are just some examples - Dijkstra’s algorithm has widespread utility due to its flexibility and guarantees on finding shortest paths. Next, we will go through the algorithm logic and pseudocode.

Dijkstra’s Algorithm Pseudocode

The pseudocode below outlines the overall logic and steps carried out by Dijkstra’s algorithm to find the shortest paths from a single source vertex to all other vertices in the graph:

function Dijkstra(Graph, source):

  dist[source] ← 0                            // Distance from source to source is set to 0

  prev[source] ← undefined                    // No previous vertex defined for source vertex yet

  create vertex set Q

  for each vertex v in Graph:

    if v ≠ source:

      dist[v] ← INFINITY                       // Unknown distance from source to v, set to infinity

      prev[v] ← undefined                      // Previous vertex for each vertex undefined

    add v to Q                                 // All nodes initially in Q

  while Q is not empty:

    u ← vertex in Q with min dist[u]           // Vertex with the least dist value

    remove u from Q

    for each neighbor v of u:

      alt ← dist[u] + length(u, v)             // Alternative path distance

      if alt < dist[v]:                        // If alternative path is shorter

        dist[v] ← alt

        prev[v] ← u                            // Update vertex previous

  return dist[], prev[]                        // Returns shortest path distances and previous vertices

The key steps are:

  1. Initialize graph distance values - Set source distance to 0 and all other vertices to infinity.

  2. Add all vertices to priority queue Q - Used to select closest vertex.

  3. Loop while Q not empty:

    • Select closest vertex u from Q and remove from Q.
    • Update distances of all neighboring vertices of u.
  4. Return final dist[] and prev[].

Now let’s implement this algorithm in Python step-by-step.

Implementing Dijkstra’s Algorithm in Python

We will use Python to implement a program that calculates Dijkstra’s algorithm for a weighted directed graph. Our program will find and print the shortest path distances from the source vertex to all vertices in the graph. Follow along with the detailed code walkthrough below.

Import Modules

We import several modules that will be used:

import heapq
from collections import defaultdict

Graph Representation

We will represent the weighted directed graph as an adjacency list using a Python dictionary. Each key is a vertex and the value is a list of tuples representing connected neighbor vertices and edge weights:

graph = {
  'A': [('B', 2), ('C', 5)],
  'B': [('A', 2), ('D', 3), ('E', 1)],
  'C': [('A', 5), ('E', 1)],
  'D': [('B', 3)],
  'E': [('B', 1), ('C', 1)]
}

This allows simple lookup of neighbors for each vertex in the graph.

Initialize Graph

We initialize the graph distances and previous vertices for every vertex to default values:

dist = defaultdict(lambda: float("inf"))
prev = defaultdict(lambda: None)

The distances use infinity and previous vertices are None to start.

Define Vertex and Edge Classes

For our priority queue, we will create simple Vertex and Edge classes to store vertex names and edge weights:

class Vertex:
  def __init__(self, name):
    self.name = name

  def __repr__(self):
      return f"{self.name}"

class Edge:
  def __init__(self, weight, start, target):
    self.weight = weight
    self.start = start
    self.target = target

This allows us to use vertices and edges interchangeably in our queues and edge lookups.

Create Priority Queue

We implement the priority queue Q using Python’s heapq module. The queue will store vertices based on shortest path distances:

Q = []
heapq.heapify(Q)

heapq.heapify converts the list Q to a min heap to allow efficient O(log n) insertion and extraction of the closest vertex.

Dijkstra’s Algorithm Function

Here is the main Dijkstra’s algorithm function:

def dijkstra(graph, source):

  def relax(edge):
    """Update vertex distances"""
    if dist[edge.target] > dist[edge.start] + edge.weight:
      dist[edge.target] = dist[edge.start] + edge.weight
      prev[edge.target] = edge.start

  dist[source] = 0

  Q.append(Vertex(source))

  while Q:

    u = heapq.heappop(Q)

    if u in graph:

      for v in graph[u]:

        relax(Edge(v[1], u, v[0]))

        if v[0] not in [x.name for x in Q]:

          heapq.heappush(Q, Vertex(v[0]))

  return dist, prev

It takes the graph and source vertex as input. Key steps:

  1. Initialize source vertex distance to 0 and add to Q.

  2. Pop closest vertex from Q and relax edges - update neighbor distances.

  3. Add neighbors to Q if not visited.

  4. Return final dist and prev dicts when Q empty.

Main Function

The main function handles taking inputs, calling the algorithm, and printing formatted output:

source = 'A'
dist, prev = dijkstra(graph, source)

print(f"Shortest distances from {source}:")
for k,v in dist.items():
  print(f"{k}: {v}")

print("\nShortest paths:")
for dest,dist in dist.items():
  path = [dest]
  prev_vertex = prev[dest]
  while prev_vertex is not None:
    path.append(prev_vertex)
    prev_vertex = prev[prev_vertex]
  print(f"{source} -> {' -> '.join(reversed(path))}")

It prints the shortest path distances and actual shortest paths from source to each vertex.

Testing and Output

We can test our implementation on the example graph:

Shortest distances from A:
A: 0
B: 2
C: 4
D: 5
E: 3

Shortest paths:
A -> A
A -> B -> A
A -> C -> A
A -> B -> D -> B -> A
A -> B -> E -> C -> A

This matches the expected results! Our program successfully found the shortest paths from vertex A to every other vertex.

Dijkstra’s Algorithm Code Example

Here is the full code example for reference:

import heapq
from collections import defaultdict

class Vertex:
  # vertex class definition

class Edge:
  # edge class definition

def dijkstra(graph, source):

  # dijkstra function

graph = {
  # sample graph
}

source = 'A'
dist, prev = dijkstra(graph, source)

# Print results

Analysis of Time and Space Complexity

The time and space complexity of Dijkstra’s algorithm depends on the graph representation and priority queue implementation:

So in summary, the time complexity is O(V^2) with a basic priority queue implementation or O( (V + E) log V) using a binary heap queue. The standard adjacency list graph representation uses O(V + E) space.

These can be optimized further as discussed next.

Optimizations for Dijkstra’s Algorithm

Some optimizations can improve performance of Dijkstra’s algorithm:

The core algorithm logic remains the same, but performance is improved through more advanced queueing data structures and search techniques.

Practical Examples and Applications

Here are some examples of applying Dijkstra’s algorithm to real-world use cases:

Routing Traffic on Road Network Graph

Model road intersections as vertices and road segments between them as weighted edges based on distance. Run Dijkstra’s algorithm to find shortest path routes between start and destination points. This is how mapping and GPS apps calculate ETAs.

Network Packet Routing

Use Dijkstra’s to determine lowest latency paths for network traffic. Weights represent costs between routers. Can route around failures or congestion for robustness. Enables efficient video conferencing and VOIP calls.

Robot Path Planning

Represent robot motion as movement along graph edges with costs reflecting terrain difficulty. Dijkstra’s algorithm calculates optimal feasible path through a map from current position to desired goal position while avoiding obstacles.

Board Game AI

Model game boards as graphs with edges connecting valid movements and turn transitions. Edge weights depend on game scoring. AI players can use Dijkstra’s to determine high-scoring move sequences and strategies.

Scheduling Job Dependencies

Map job dependencies into directed acyclic graph with edge weights as job durations. Running Dijkstra’s algorithm generates optimal schedule that minimizes total makespan. Useful for project management.

Common Interview Questions

Here are some common interview questions about Dijkstra’s algorithm:

Q: Explain Dijkstra’s algorithm and how it works.

A: Dijkstra’s algorithm is a graph search algorithm that finds the shortest paths from a source vertex to all other vertices in the graph. It works by starting with the source vertex, then iteratively exploring outward based on closest unvisited vertices until it finds the shortest paths to all vertices. At each step, it relaxes edges to update distance scores.

Q: What is the time and space complexity of Dijkstra’s algorithm?

A: The time complexity is O(V^2) with a basic array/linked list priority queue, or O((V+E)log V) with a binary heap queue. The space complexity is O(V+E) to store the graph adjacency list, plus O(V) for queue.

Q: How is Dijkstra’s algorithm different from breadth-first search?

A: BFS explores vertices in uniform order across all branches while Dijkstra’s prioritizes vertices with shortest distances. Dijkstra is more suited for weighted graphs and guarantees shortest path distances.

Q: When would Dijkstra’s algorithm not work or not find shortest path?

A: Dijkstra’s algorithm fails if edge weights are negative. It also won’t work for finding shortest paths in disconnected graphs where some vertices are unreachable from the source.

Q: What are some ways Dijkstra’s algorithm can be optimized?

A: Optimizations include A* search, bidirectional search, caching previous results, using better priority queues, preprocessing graph, and parallelization.

Summary

In this guide, we covered how to implement Dijkstra’s shortest path algorithm in Python step-by-step including the key logic, pseudocode, data structures, optimizations, time complexity analysis, and sample code.

Dijkstra’s algorithm is a fundamental graph search algorithm used in many applications that require finding optimal shortest paths on networks. It works by propagating distance scores outward from the source vertex, relaxing edges to update distances iteratively until the shortest path tree is fully constructed.

While a brute force breadth-first search could also calculate shortest paths, Dijkstra’s algorithm uses priority queue ordering to minimize the vertices explored for improved efficiency. It also guarantees shortest path distances are found for all graph vertices.

When implemented using a min heap priority queue for O((V+E)log V) performance and clever optimizations, Dijkstra’s algorithm can solve the single-source shortest paths problem efficiently even on large real-world graphs. This makes it an indispensable algorithm for GPS navigation, network routing, and many other pathfinding applications.