Using Dijkstra’s Algorithm to Find All Shortest Paths in a Graph
Want to share your content on python-bloggers? click here.
I always had trouble spelling Dijkstra correctly until someone pointed out “It’s just D, followed by i-j-k, then stra”. Once it was pointed out that i-j-k is represented sequentially in alphabetical order, spelling Dijkstra becomes trival – almost fun!
Dijkstra’s algorithm is one of the most well-known and studied graph algorithms for finding the shortest path between a set of vertices. For a specified starting node, the algorithm finds the shortest path between the source vertex and all other vertices in the graph. Dijkstra’s cannot handle graphs with negative edge weights: For graphs with negative weight edges, Floyd-Warshall or Bellman Ford can be used. But for graphs with positive edge weights, Dijkstra’s has better worst case performance than more general alternatives (O((n + m)log(n)) for Dijkstra vs. O(mn) for Bellman-Ford and O(n^3) for Floyd-Warshall).
However, Dijkstra’s only returns a single shortest path. In many cases a single shortest path is enough. But there are plenty of applications in which knowledge of alternate minimum weight paths can be useful. If a graph has multiple shortest paths, it is necessary to add supplementary logic to identify and return all such paths. The modified routine to return all shortest paths from graph G is described below:
- Run Dijkstra’s starting from start node a. Returns shortest path and distance to every node from vertex a. 
- Run Dijkstra’s starting from end node f. Returns shortest path and distance to every node from vertex f. 
- Let - dist_a2f= A shortest path from vertex a to vertex f.
- For (u, v) in G.edges: - Let - w_uv= Weight of edge (u, v).- Let - dist_a2u= Distance from start vertex a to vertex u.- Let - dist_f2v= Distance from end vertex f to vertex v.- If - dist_a2u+- w_uv+- dist_f2v==- dist_a2f:- Then edge (u, v) is on some minimal weight path. 
Lets use networkx to create a graph with multiple shortest paths:
# Create network with multiple minimal weight paths.
import matplotlib.pyplot as plt
import networkx as nx
seed = 518
G = nx.Graph()
G.add_edge("a", "b", weight=4)
G.add_edge("b", "c", weight=2)
G.add_edge("a", "c", weight=6)
G.add_edge("c", "d", weight=1)
G.add_edge("d", "e", weight=3)
G.add_edge("e", "f", weight=2)
G.add_edge("c", "f", weight=6)
G.add_edge("a", "d", weight=9)
G.add_edge("f", "d", weight=10)
edges = [(u, v) for (u, v, d) in G.edges(data=True)]
color_map = []
for g in G:
    if g=="a":
        color_map.append("green")
    elif g=="f":
        color_map.append("red")
    else:
        color_map.append("blue")
# edge weight labels.
edge_labels = nx.get_edge_attributes(G, "weight")
fig, ax = plt.subplots(1, 1, figsize=(8, 6), tight_layout=True)
pos = nx.spring_layout(G, seed=seed)
nx.draw_networkx_nodes(G, pos, node_size=800, node_color=color_map)
nx.draw_networkx_edges(G, pos, edgelist=edges, width=2, alpha=0.5, edge_color="grey", style="solid")
nx.draw_networkx_labels(G, pos, font_size=16, font_color="white", font_weight="bold", font_family="sans-serif")
nx.draw_networkx_edge_labels(G, pos, edge_labels, font_size=12)
plt.axis("off")
plt.show();
G has three shortest paths of weight/distance 12:
- a - c - f
- a - b - c - f
- a - c - d - e - f
Running single_source_dijkstra from networkx returns the distance to each vertex in a graph from a specified starting vertex (in our example, vertex a). single_source_dijkstra returns two dictionaries: The first stores distances to each vertex; the second stores the shortest path from the start node to all other vertices (d2v0 and p2v0 in the code below):
d2v0, p2v0 = nx.single_source_dijkstra(G, "a")
print(f"d2v0: {d2v0}")
print(f"p2v0: {p2v0}")d2v0: {'a': 0, 'b': 4, 'c': 6, 'd': 7, 'e': 10, 'f': 12}
p2v0: {'a': ['a'], 'b': ['a', 'b'], 'c': ['a', 'c'], 'd': ['a', 'c', 'd'], 'f': ['a', 'c', 'f'], 'e': ['a', 'c', 'd', 'e']}single_source_dijkstra only returns a - c - f. To also identify a - b - c - f and a - c - d - e - f, we implement the steps from our pseudocode:
# Idenitfy all shortests paths in graph G.
all_shortest_paths = set()
# d2v: Distance to vertex (from start node a).
# p2v: Shortest path to vertext (from start node a).
d2v0, p2v0 = nx.single_source_dijkstra(G, "a")
d2v1, p2v1 = nx.single_source_dijkstra(G, "f")
all_shortest_paths.add(tuple(p2v0["f"]))
# Iterate through all edges. Check for additional shortest paths. 
# Distance of a shortest path from a to f. 
dist_a2f = d2v0["f"]
for u, v, dw in G.edges(data=True):
    
    # Get weight of current edge spanning (u, v)
    w_uv = dw["weight"]
    
    # Get distance from start vertex to u.
    dist_a2u = d2v0[u]
    
    # Get distance from end vertex to v.
    dist_f2v = d2v1[v]
    
    if dist_a2u + w_uv + dist_f2v == dist_a2f:
        
        # Edge uv is on some minimal weight path. Append to all_shortest_paths.
        pp = tuple(p2v0[u] + p2v1[v][::-1])
        all_shortest_paths.add(pp)Shortest paths are stored in a set of ordered tuples, with each tuple representing the vertices of some path of minimum weight. Printing the contents of all_shortest_paths yields:
all_shortest_paths
{('a', 'b', 'c', 'f'), ('a', 'c', 'd', 'e', 'f'), ('a', 'c', 'f')}The time complexity for two separate invocations of Dijkstra’s is 2 * O((n + m)log(n)), and O(m + n) to iterate through the adjacency list checking for intermediate edges falling on a path of minimum weight.
Want to share your content on python-bloggers? click here.