Skip to content

Dijkstra's Algorithm

Dijkstra's algorithm finds the shortest paths from a single source vertex to all other vertices in a weighted graph with non-negative edge weights.

Algorithm Overview

Dijkstra's algorithm uses a greedy approach with a priority queue to efficiently find shortest paths. It maintains a set of vertices whose shortest distance from the source is known, and progressively adds vertices with the minimum tentative distance.

Key Features

  • Single-Source: Finds shortest paths from one source to all other vertices
  • Non-Negative Weights: Requires all edge weights to be non-negative
  • Optimal: Guarantees finding the actual shortest paths
  • Efficient: O((V + E) log V) time complexity with binary heap

Available Functions

Usage Examples

Basic Shortest Path

from algo.data_structs.graphs import AdjacencyListGraph
from algo.algorithms.graphs import dijkstra

# Create a transportation network
network = AdjacencyListGraph(directed=True)
network.add_edge('Home', 'Work', 15)      # 15 minutes
network.add_edge('Home', 'Store', 10)     # 10 minutes  
network.add_edge('Store', 'Work', 8)      # 8 minutes
network.add_edge('Work', 'Gym', 5)        # 5 minutes
network.add_edge('Store', 'Gym', 12)      # 12 minutes

# Find shortest times from Home to all locations
distances = dijkstra(network, 'Home')

print("Shortest travel times from Home:")
for location, time in distances.items():
    if time != float('inf'):
        print(f"  {location}: {time} minutes")

# Output:
# Home: 0 minutes
# Store: 10 minutes  
# Work: 18 minutes (Home -> Store -> Work)
# Gym: 23 minutes (Home -> Store -> Work -> Gym)

Path Reconstruction

from algo.algorithms.graphs import dijkstra_with_path

# Find specific route with path details
result = dijkstra_with_path(network, 'Home', 'Gym')

if result:
    distance, path = result
    print(f"Best route to Gym: {' -> '.join(path)}")
    print(f"Total time: {distance} minutes")

    # Analyze the route
    for i in range(len(path) - 1):
        from_loc = path[i]
        to_loc = path[i + 1]
        segment_time = network.get_edge_weight(from_loc, to_loc)
        print(f"  {from_loc} to {to_loc}: {segment_time} minutes")

Multiple Destinations

from algo.algorithms.graphs import dijkstra_all_paths

# Plan trips to multiple destinations
destinations = dijkstra_all_paths(network, 'Home')

print("All possible trips from Home:")
for location, (distance, path) in destinations.items():
    if location != 'Home':
        print(f"\nTo {location}:")
        print(f"  Route: {' -> '.join(path)}")
        print(f"  Time: {distance} minutes")
        print(f"  Stops: {len(path) - 1}")

Network Analysis

# Analyze network connectivity and efficiency
def analyze_network_efficiency(graph):
    """Analyze average travel times and connectivity."""
    vertices = list(graph.get_vertices())
    total_reachable_pairs = 0
    total_distance = 0
    max_distance = 0

    for start in vertices:
        distances = dijkstra(graph, start)

        for end, distance in distances.items():
            if start != end and distance != float('inf'):
                total_reachable_pairs += 1
                total_distance += distance
                max_distance = max(max_distance, distance)

    if total_reachable_pairs > 0:
        avg_distance = total_distance / total_reachable_pairs
        connectivity = total_reachable_pairs / (len(vertices) * (len(vertices) - 1))

        return {
            'average_distance': avg_distance,
            'max_distance': max_distance,
            'connectivity': connectivity,
            'reachable_pairs': total_reachable_pairs
        }

    return {'connectivity': 0}

analysis = analyze_network_efficiency(network)
print(f"\nNetwork Analysis:")
print(f"  Average travel time: {analysis['average_distance']:.1f} minutes")
print(f"  Maximum travel time: {analysis['max_distance']} minutes")
print(f"  Connectivity: {analysis['connectivity']:.1%}")

Handling Edge Cases

# Check for algorithm compatibility
from algo.algorithms.graphs import has_negative_weights

if has_negative_weights(network):
    print("Warning: Graph has negative weights. Use Floyd-Warshall instead.")
else:
    print("Graph is compatible with Dijkstra's algorithm.")

# Handle unreachable destinations
result = dijkstra_with_path(network, 'Gym', 'Home')
if result is None:
    print("No path exists from Gym to Home")
else:
    distance, path = result
    print(f"Return trip: {' -> '.join(path)} ({distance} minutes)")

# Handle non-existent vertices
distances = dijkstra(network, 'NonExistent')
if not distances:
    print("Start vertex does not exist in graph")

Real-World Applications

GPS Navigation System

# Model a city street network
city = AdjacencyListGraph(directed=True)

# Add streets with travel times (in minutes)
streets = [
    ('1st_Ave', '2nd_Ave', 3),
    ('2nd_Ave', '3rd_Ave', 4),
    ('1st_Ave', 'Main_St', 2),
    ('Main_St', '2nd_Ave', 2),
    ('2nd_Ave', 'Park_St', 5),
    ('3rd_Ave', 'Park_St', 3),
]

for from_street, to_street, time in streets:
    city.add_edge(from_street, to_street, time)

# Find fastest route
route = dijkstra_with_path(city, '1st_Ave', 'Park_St')
if route:
    time, path = route
    print(f"Fastest route: {' -> '.join(path)} ({time} min)")

Network Routing

# Model computer network with latencies
network_topology = AdjacencyListGraph(directed=False)

# Add network links with latencies (ms)
links = [
    ('Router_A', 'Router_B', 10),
    ('Router_B', 'Router_C', 15),
    ('Router_A', 'Router_D', 20),
    ('Router_D', 'Router_C', 5),
    ('Router_C', 'Server', 8),
]

for router1, router2, latency in links:
    network_topology.add_edge(router1, router2, latency)

# Find lowest latency path to server
path_info = dijkstra_with_path(network_topology, 'Router_A', 'Server')
if path_info:
    latency, path = path_info
    print(f"Optimal route to server: {' -> '.join(path)}")
    print(f"Total latency: {latency}ms")

Performance Characteristics

Time Complexity

  • Binary Heap: O((V + E) log V)
  • Fibonacci Heap: O(E + V log V) - theoretical optimum
  • Array Implementation: O(V²) - suitable for dense graphs

Space Complexity

  • Memory Usage: O(V) for distances and priority queue
  • Path Storage: O(V²) for all paths (dijkstra_all_paths)

Graph Type Impact

Graph Type Time Complexity Best Use Case
Sparse (E ≈ V) O(V log V) Large networks with few connections
Dense (E ≈ V²) O(V² log V) Consider Floyd-Warshall for all-pairs

Algorithm Limitations

Negative Weights

Dijkstra's algorithm does not work with negative edge weights:

# This will produce incorrect results
bad_graph = AdjacencyListGraph(directed=True)
bad_graph.add_edge('A', 'B', 10)
bad_graph.add_edge('A', 'C', 5)
bad_graph.add_edge('C', 'B', -8)  # Negative weight!

# Always check before using Dijkstra
if has_negative_weights(bad_graph):
    print("Use Floyd-Warshall or Bellman-Ford for negative weights")

Single Source

For all-pairs shortest paths, consider Floyd-Warshall:

# Inefficient: Running Dijkstra for each vertex
vertices = list(graph.get_vertices())
all_distances = {}
for start in vertices:
    all_distances[start] = dijkstra(graph, start)

# Better: Use Floyd-Warshall for all-pairs
from algo.algorithms.graphs import floyd_warshall
all_distances = floyd_warshall(graph)

Best Practices

  1. Validate Input: Check for negative weights before running
  2. Choose Right Variant: Use specific functions based on your needs
  3. Handle Edge Cases: Check for None returns and infinite distances
  4. Consider Alternatives: Floyd-Warshall for small dense graphs or negative weights
  5. Memory Management: Use dijkstra() instead of dijkstra_all_paths() for large graphs when you don't need all paths

API Reference

Dijkstra's algorithm for finding shortest paths from a single source vertex.

This module provides an implementation of Dijkstra's algorithm that works with any graph implementation following the Graph interface.

dijkstra(graph, start)

Find shortest paths from a source vertex to all other vertices using Dijkstra's algorithm.

Parameters:

Name Type Description Default
graph Graph

A graph implementation following the Graph interface

required
start Any

Starting vertex

required

Returns:

Type Description
Dict[Any, float]

A dictionary mapping vertices to their shortest distances from start

Time Complexity: O((V + E) log V) where V is vertices and E is edges Space Complexity: O(V)

Note: This algorithm doesn't work with negative edge weights.

dijkstra_all_paths(graph, start)

Find shortest paths from a source vertex to all other reachable vertices.

Parameters:

Name Type Description Default
graph Graph

A graph implementation following the Graph interface

required
start Any

Starting vertex

required

Returns:

Type Description
Dict[Any, Tuple[float, List[Any]]]

A dictionary mapping vertices to (distance, path) tuples

dijkstra_with_path(graph, start, end)

Find the shortest path between two vertices using Dijkstra's algorithm with path reconstruction.

Parameters:

Name Type Description Default
graph Graph

A graph implementation following the Graph interface

required
start Any

Starting vertex

required
end Any

Ending vertex

required

Returns:

Type Description
Optional[Tuple[float, List[Any]]]

A tuple of (distance, path) if path exists, None otherwise

Optional[Tuple[float, List[Any]]]

The path is a list of vertices from start to end

has_negative_weights(graph)

Check if the graph has any negative edge weights.

Dijkstra's algorithm doesn't work correctly with negative weights.

Parameters:

Name Type Description Default
graph Graph

A graph implementation following the Graph interface

required

Returns:

Type Description
bool

True if any edge has negative weight, False otherwise