1
+ from collections import deque
2
+ import heapq
3
+ import sys
4
+
5
+ #First implementation of johnson algorithm
6
+ class JohnsonGraph :
7
+ def __init__ (self ):
8
+ self .edges = []
9
+ self .graph = {}
10
+
11
+ #add vertices for a graph
12
+ def add_vertices (self , u ):
13
+ self .graph [u ] = []
14
+
15
+ #assign weights for each edges formed of the directed graph
16
+ def add_edge (self , u , v , w ):
17
+ self .edges .append ((u , v , w ))
18
+ self .graph [u ].append ((v ,w ))
19
+
20
+ #perform a dijkstra algorithm on a directed graph
21
+ def dijkstra (self , s ):
22
+ no_v = len (self .graph )
23
+ distances = {vertex : sys .maxsize - 1 for vertex in self .graph }
24
+ pq = [(0 ,s )]
25
+
26
+ distances [s ] = 0
27
+ while pq :
28
+ weight , v = heapq .heappop (pq )
29
+
30
+ if weight > distances [v ]:
31
+ continue
32
+
33
+ for node , w in self .graph [v ]:
34
+ if distances [v ]+ w < distances [node ]:
35
+ distances [node ] = distances [v ]+ w
36
+ heapq .heappush (pq , (distances [node ], node ))
37
+ return distances
38
+
39
+ #carry out the bellman ford algorithm for a node and estimate its distance vector
40
+ def bellman_ford (self , s ):
41
+ no_v = len (self .graph )
42
+ distances = {vertex : sys .maxsize - 1 for vertex in self .graph }
43
+ distances [s ] = 0
44
+
45
+ for u in self .graph :
46
+ for u , v , w in self .edges :
47
+ if distances [u ] != sys .maxsize - 1 and distances [u ]+ w < distances [v ]:
48
+ distances [v ] = distances [u ]+ w
49
+
50
+ return distances
51
+
52
+ #perform the johnson algorithm to handle the negative weights that could not be handled by either the dijkstra
53
+ #or the bellman ford algorithm efficiently
54
+ def johnson_algo (self ):
55
+
56
+ self .add_vertices ("#" )
57
+ for v in self .graph :
58
+ if v != "#" :
59
+ self .add_edge ("#" , v , 0 )
60
+
61
+ n = self .bellman_ford ("#" )
62
+
63
+ for i in range (len (self .edges )):
64
+ u , v , weight = self .edges [i ]
65
+ self .edges [i ] = (u , v , weight + n [u ] - n [v ])
66
+
67
+ self .graph .pop ("#" )
68
+ self .edges = [(u , v , w ) for u , v , w in self .edges if u != "#" ]
69
+
70
+ for u in self .graph :
71
+ self .graph [u ] = [(v , weight ) for x , v , weight in self .edges if x == u ]
72
+
73
+ distances = []
74
+ for u in self .graph :
75
+ new_dist = self .dijkstra (u )
76
+ for v in self .graph :
77
+ if new_dist [v ] < sys .maxsize - 1 :
78
+ new_dist [v ] += n [v ] - n [u ]
79
+ distances .append (new_dist )
80
+ return distances
81
+
82
+ g = JohnsonGraph ()
83
+ #this a complete connected graph
84
+ g .add_vertices ("A" )
85
+ g .add_vertices ("B" )
86
+ g .add_vertices ("C" )
87
+ g .add_vertices ("D" )
88
+ g .add_vertices ("E" )
89
+
90
+ g .add_edge ("A" , "B" , 1 )
91
+ g .add_edge ("A" , "C" , 3 )
92
+ g .add_edge ("B" , "D" , 4 )
93
+ g .add_edge ("D" , "E" , 2 )
94
+ g .add_edge ("E" , "C" , - 2 )
95
+
96
+
97
+ optimal_paths = g .johnson_algo ()
98
+ print ("Print all optimal paths of a graph using Johnson Algorithm" )
99
+ for i , row in enumerate (optimal_paths ):
100
+ print (f"{ i } : { row } " )
0 commit comments