The Dijkstra's Shortest Path First algorithm is one solution to the problem of finding the shortest paths between nodes in a graph.
This algorithm implements a so called Greedy solution, since will follow the best possible choice at each step.
Python3 implementation: dijkstra.py
Note - This implementation of the Dijkstra's algorithm is not very efficient. Currently it has a
-
Declare and initialize result, unvisited, and path
- Create a
resultdictionary, which is used to preserve the shortest distance for all nodes in the graph. The distance to all other nodes from the source is unknown, therefore is set to infinity. - Create a set
unvisitedcontaining nodes that have not been visited, initialized with all nodes of the graph. Start with the source node, where the distance from source to source itself is 0. - Create a
pathdictionary that keeps track of the previous node that can lead to the current node.
- Create a
-
As long as
unvisitedis non-empty, repeat the following:- Find the unvisited node having smallest known distance from the source node.
- For the current node, find all the unvisited neighbours.
- If the calculated distance is less than the already known distance saved in
resultdictionary, we update the distance as well as the path. - Remove the current node from the
unvisitedset.
First, we need a basic implementation of a Undirected Acyclic Graph using an adjacency dictionary, that contain:
- a set of nodes for efficient lookups
- an Adjacency dictionary using a defaultdict(), so we can easly lookup at non existent keys.
- a Distances dictionary, that contain the solution.
class Graph:
def __init__(self):
# A set cannot contain duplicate nodes
self.nodes = set()
# Defaultdict provides a default value for a key that does not exists
self.neighbours = defaultdict(list)
# Dictionary where key are (source, dest) and value is the distance
self.distances = {}
def add_node(self, value):
self.nodes.add(value)
def add_edge(self, from_node, to_node, distance):
self.neighbours[from_node].append(to_node)
self.neighbours[to_node].append(from_node)
# Since the graph is undirected
self.distances[(from_node, to_node)] = distance
self.distances[(to_node, from_node)] = distance Python3 implementation: dijkstra.py
Note - This implementation of the Dijkstra's algorithm is not very efficient. Currently it has a
-
Declare
distancesandshortest_distancesdictionaries:- Create a
distancesdictionary containing all the nodes of the graph that have not been visited, with a value of 0 if the source, Otherwise infinite. - Create an empty
shortest_distancesdictionary, which is used to preserve the shortest distance found so far.
- Create a
-
As long as
distancesis non-empty, repeat the following:- Sort the
distancesdictionary by ascending distance - Move the node with the smallest distance to the
shortest_distancedictionary - For each
neighbourof node:- If the distance is smaller than the already stored distance, update the distances dictionary.
- Sort the
We need some helper object, a Graph, a GraphNode and a GraphEdge to represent the graph:
class GraphEdge:
def __init__(self, destinationNode, distance):
self.node = destinationNode
self.distance = distance
class GraphNode:
def __init__(self, val = None):
self.value = val
self.edges = []
def add_child(self, node, distance):
self.edges.append(GraphEdge(node, distance))
class Graph:
def __init__(self, adjacent_list = None):
self.nodes = defaultdict(GraphNode)
if adjacent_list is not None:
self._read_adjacent_list(adjacent_list)
def _read_adjacent_list(self, adjacent_list):
for elem in adjacent_list:
self.nodes[elem[0]].value = elem[0]
self.nodes[elem[1]].value = elem[1]
self.add_edge(self.nodes[elem[0]], self.nodes[elem[1]], elem[2])
def add_edge(self, node1, node2, distance):
if node1.value in self.nodes and node2.value in self.nodes:
node1.add_child(node2, distance)
node2.add_child(node1, distance)We can now implement the Dijkstra algorithm,
def dijkstra(graph, start):
# Dictionary with distances to all nodes, initially set all to infinity
distances = {v:0 if k == start else math.inf for k, v in graph.nodes.items()}
# Dictionary with the "shortest" distance to all nodes
shortest_distance = {}
while distances:
# Sort the distances dictionary by ascending distance
node, distance = sorted(distances.items(), key=lambda x: x[1])[0]
# Move the node with the smallest distance to shortest_distance
shortest_distance[node.value] = distances.pop(node)
# For each neighbour of node, if the distance is smaller than the
# already stored distance, update the distances dictionary
for edge in node.edges:
if edge.node in distances:
distance_to_neighbour = distance + edge.distance
if distances[edge.node] > distance_to_neighbour:
distances[edge.node] = distance_to_neighbour
return shortest_distance