@@ -16,169 +16,169 @@ class Graph:
16
16
"""
17
17
A lightweight graph class for algorithms that don't have access to the main Graph class.
18
18
"""
19
-
19
+
20
20
def __init__ (self ):
21
21
self .adjacency_list = defaultdict (list )
22
22
self .vertices = set ()
23
-
23
+
24
24
def add_edge (self , source : Any , destination : Any , weight : float ) -> None :
25
25
self .adjacency_list [source ].append ((destination , weight ))
26
26
self .vertices .add (source )
27
27
self .vertices .add (destination )
28
-
28
+
29
29
def get_neighbors (self , vertex : Any ) -> List [Tuple [Any , float ]]:
30
30
return self .adjacency_list .get (vertex , [])
31
-
31
+
32
32
def get_vertices (self ) -> set :
33
33
return self .vertices .copy ()
34
-
34
+
35
35
def has_vertex (self , vertex : Any ) -> bool :
36
36
return vertex in self .vertices
37
-
37
+
38
38
def get_vertex_count (self ) -> int :
39
39
return len (self .vertices )
40
40
41
41
42
42
class BellmanFord :
43
43
"""
44
44
Bellman-Ford algorithm implementation for single-source shortest path.
45
-
45
+
46
46
This algorithm can handle graphs with negative edge weights and can detect
47
47
negative cycles. It's particularly useful as a preprocessing step in
48
48
Johnson's algorithm.
49
49
"""
50
-
50
+
51
51
def __init__ (self , graph ):
52
52
"""
53
53
Initialize the Bellman-Ford algorithm with a graph.
54
-
54
+
55
55
Args:
56
56
graph: The weighted directed graph to process
57
57
"""
58
58
self .graph = graph
59
59
self .distances = {}
60
60
self .predecessors = {}
61
-
61
+
62
62
def find_shortest_paths (self , start_vertex : Any ) -> Optional [Dict [Any , float ]]:
63
63
"""
64
64
Find shortest paths from start_vertex to all other vertices.
65
-
65
+
66
66
Args:
67
67
start_vertex: The source vertex to start from
68
-
68
+
69
69
Returns:
70
70
Dictionary of vertex -> shortest distance, or None if negative cycle exists
71
71
"""
72
72
if not self .graph .has_vertex (start_vertex ):
73
73
raise ValueError (f"Start vertex { start_vertex } not found in graph" )
74
-
74
+
75
75
# Initialize distances
76
- self .distances = {vertex : float (' inf' ) for vertex in self .graph .get_vertices ()}
76
+ self .distances = {vertex : float (" inf" ) for vertex in self .graph .get_vertices ()}
77
77
self .distances [start_vertex ] = 0
78
78
self .predecessors = {vertex : None for vertex in self .graph .get_vertices ()}
79
-
79
+
80
80
# Relax edges V-1 times
81
81
vertex_count = self .graph .get_vertex_count ()
82
-
82
+
83
83
for iteration in range (vertex_count - 1 ):
84
84
updated = False
85
-
85
+
86
86
for vertex in self .graph .get_vertices ():
87
- if self .distances [vertex ] != float (' inf' ):
87
+ if self .distances [vertex ] != float (" inf" ):
88
88
for neighbor , weight in self .graph .get_neighbors (vertex ):
89
89
new_distance = self .distances [vertex ] + weight
90
-
90
+
91
91
if new_distance < self .distances [neighbor ]:
92
92
self .distances [neighbor ] = new_distance
93
93
self .predecessors [neighbor ] = vertex
94
94
updated = True
95
-
95
+
96
96
# Early termination if no updates in this iteration
97
97
if not updated :
98
98
break
99
-
99
+
100
100
# Check for negative cycles
101
101
if self ._has_negative_cycle ():
102
102
return None
103
-
103
+
104
104
return self .distances .copy ()
105
-
105
+
106
106
def _has_negative_cycle (self ) -> bool :
107
107
"""
108
108
Check if the graph contains a negative cycle.
109
-
109
+
110
110
Returns:
111
111
True if negative cycle exists, False otherwise
112
112
"""
113
113
for vertex in self .graph .get_vertices ():
114
- if self .distances [vertex ] != float (' inf' ):
114
+ if self .distances [vertex ] != float (" inf" ):
115
115
for neighbor , weight in self .graph .get_neighbors (vertex ):
116
116
if self .distances [vertex ] + weight < self .distances [neighbor ]:
117
117
return True
118
118
return False
119
-
119
+
120
120
def get_path (self , start_vertex : Any , end_vertex : Any ) -> Optional [List [Any ]]:
121
121
"""
122
122
Get the shortest path from start_vertex to end_vertex.
123
-
123
+
124
124
Args:
125
125
start_vertex: Source vertex
126
126
end_vertex: Destination vertex
127
-
127
+
128
128
Returns:
129
129
List of vertices representing the path, or None if no path exists
130
130
"""
131
131
if not self .distances or end_vertex not in self .distances :
132
132
return None
133
-
134
- if self .distances [end_vertex ] == float (' inf' ):
133
+
134
+ if self .distances [end_vertex ] == float (" inf" ):
135
135
return None
136
-
136
+
137
137
path = []
138
138
current = end_vertex
139
-
139
+
140
140
while current is not None :
141
141
path .append (current )
142
142
current = self .predecessors .get (current )
143
-
143
+
144
144
path .reverse ()
145
-
145
+
146
146
# Verify the path starts with start_vertex
147
147
if path [0 ] != start_vertex :
148
148
return None
149
-
149
+
150
150
return path
151
-
151
+
152
152
def get_distance (self , vertex : Any ) -> float :
153
153
"""
154
154
Get the shortest distance to a specific vertex.
155
-
155
+
156
156
Args:
157
157
vertex: The target vertex
158
-
158
+
159
159
Returns:
160
160
Shortest distance to the vertex
161
161
"""
162
- return self .distances .get (vertex , float (' inf' ))
163
-
162
+ return self .distances .get (vertex , float (" inf" ))
163
+
164
164
@staticmethod
165
165
def detect_negative_cycle (graph ) -> bool :
166
166
"""
167
167
Static method to detect if a graph contains a negative cycle.
168
-
168
+
169
169
Args:
170
170
graph: The graph to check
171
-
171
+
172
172
Returns:
173
173
True if negative cycle exists, False otherwise
174
174
"""
175
175
vertices = graph .get_vertices ()
176
176
if not vertices :
177
177
return False
178
-
178
+
179
179
# Pick any vertex as start
180
180
start_vertex = next (iter (vertices ))
181
181
bf = BellmanFord (graph )
182
182
result = bf .find_shortest_paths (start_vertex )
183
-
183
+
184
184
return result is None
0 commit comments