-
Notifications
You must be signed in to change notification settings - Fork 505
Expand file tree
/
Copy pathfloyd_warshall.py
More file actions
265 lines (203 loc) · 7.03 KB
/
floyd_warshall.py
File metadata and controls
265 lines (203 loc) · 7.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
"""
Author: ADWAITA JADHAV
Created On: 4th October 2025
Floyd-Warshall Algorithm for All-Pairs Shortest Path
Time Complexity: O(V^3) where V is the number of vertices
Space Complexity: O(V^2)
The Floyd-Warshall algorithm finds shortest paths between all pairs of vertices
in a weighted graph. It can handle negative edge weights but not negative cycles.
"""
import inspect
def floyd_warshall(graph):
"""
Find shortest paths between all pairs of vertices using Floyd-Warshall algorithm
:param graph: dictionary representing weighted graph {vertex: [(neighbor, weight), ...]}
:return: tuple (distance_matrix, next_matrix) or None if negative cycle exists
"""
if not graph:
return {}, {}
# Get all vertices
vertices = set(graph.keys())
for vertex in graph:
for neighbor, _ in graph[vertex]:
vertices.add(neighbor)
vertices = sorted(list(vertices))
n = len(vertices)
vertex_to_index = {vertex: i for i, vertex in enumerate(vertices)}
# Initialize distance and next matrices
dist = [[float('inf')] * n for _ in range(n)]
next_vertex = [[None] * n for _ in range(n)]
# Distance from vertex to itself is 0
for i in range(n):
dist[i][i] = 0
# Fill initial distances from graph
for vertex in graph:
i = vertex_to_index[vertex]
for neighbor, weight in graph[vertex]:
j = vertex_to_index[neighbor]
dist[i][j] = weight
next_vertex[i][j] = neighbor
# Floyd-Warshall main algorithm
for k in range(n):
for i in range(n):
for j in range(n):
if dist[i][k] + dist[k][j] < dist[i][j]:
dist[i][j] = dist[i][k] + dist[k][j]
next_vertex[i][j] = next_vertex[i][k]
# Check for negative cycles
for i in range(n):
if dist[i][i] < 0:
return None # Negative cycle detected
# Convert back to vertex-based dictionaries
distance_matrix = {}
next_matrix = {}
for i, vertex1 in enumerate(vertices):
distance_matrix[vertex1] = {}
next_matrix[vertex1] = {}
for j, vertex2 in enumerate(vertices):
distance_matrix[vertex1][vertex2] = dist[i][j]
next_matrix[vertex1][vertex2] = next_vertex[i][j]
return distance_matrix, next_matrix
def get_shortest_path(source, target, next_matrix):
"""
Reconstruct shortest path between two vertices
:param source: source vertex
:param target: target vertex
:param next_matrix: next matrix from Floyd-Warshall
:return: list representing the shortest path
"""
if source not in next_matrix or target not in next_matrix[source]:
return None
if next_matrix[source][target] is None:
return None # No path exists
path = [source]
current = source
while current != target:
current = next_matrix[current][target]
if current is None:
return None
path.append(current)
return path
def floyd_warshall_simple(adjacency_matrix):
"""
Floyd-Warshall algorithm using adjacency matrix representation
:param adjacency_matrix: 2D list representing weighted adjacency matrix
:return: 2D list of shortest distances or None if negative cycle
"""
if not adjacency_matrix or not adjacency_matrix[0]:
return None
n = len(adjacency_matrix)
# Check if matrix is square
for row in adjacency_matrix:
if len(row) != n:
return None
# Create a copy of the adjacency matrix
dist = [row[:] for row in adjacency_matrix]
# Floyd-Warshall algorithm
for k in range(n):
for i in range(n):
for j in range(n):
if dist[i][k] + dist[k][j] < dist[i][j]:
dist[i][j] = dist[i][k] + dist[k][j]
# Check for negative cycles
for i in range(n):
if dist[i][i] < 0:
return None
return dist
def print_distance_matrix(distance_matrix):
"""
Print the distance matrix in a readable format
:param distance_matrix: dictionary of distances
:return: string representation of the matrix
"""
if not distance_matrix:
return "Empty distance matrix"
vertices = sorted(distance_matrix.keys())
result = "Distance Matrix:\n"
# Header
result += " "
for vertex in vertices:
result += f"{vertex:>6}"
result += "\n"
# Rows
for vertex1 in vertices:
result += f"{vertex1:>4} "
for vertex2 in vertices:
dist = distance_matrix[vertex1][vertex2]
if dist == float('inf'):
result += " ∞ "
else:
result += f"{dist:>6}"
result += "\n"
return result.strip()
def find_shortest_paths_from_vertex(distance_matrix, source):
"""
Get all shortest paths from a source vertex
:param distance_matrix: distance matrix from Floyd-Warshall
:param source: source vertex
:return: dictionary of distances from source
"""
if source not in distance_matrix:
return {}
return distance_matrix[source].copy()
def find_diameter(distance_matrix):
"""
Find the diameter of the graph (longest shortest path)
:param distance_matrix: distance matrix from Floyd-Warshall
:return: diameter value
"""
if not distance_matrix:
return float('inf')
max_distance = 0
for vertex1 in distance_matrix:
for vertex2 in distance_matrix[vertex1]:
if distance_matrix[vertex1][vertex2] != float('inf'):
max_distance = max(max_distance, distance_matrix[vertex1][vertex2])
return max_distance
def create_sample_adjacency_matrix():
"""
Create a sample adjacency matrix for testing
:return: 2D list representing adjacency matrix
"""
INF = float('inf')
return [
[0, 3, INF, 7],
[8, 0, 2, INF],
[5, INF, 0, 1],
[2, INF, INF, 0]
]
def create_sample_graph():
"""
Create a sample weighted graph for testing
:return: dictionary representing a weighted graph
"""
return {
'A': [('B', 3), ('D', 7)],
'B': [('A', 8), ('C', 2)],
'C': [('A', 5), ('D', 1)],
'D': [('A', 2)]
}
def has_negative_cycle(distance_matrix):
"""
Check if the graph has a negative cycle
:param distance_matrix: distance matrix
:return: True if negative cycle exists, False otherwise
"""
if not distance_matrix:
return False
for vertex in distance_matrix:
if distance_matrix[vertex][vertex] < 0:
return True
return False
def time_complexities():
"""
Return information on time complexity
:return: string
"""
return "Best Case: O(V^3), Average Case: O(V^3), Worst Case: O(V^3)"
def get_code():
"""
Easily retrieve the source code of the floyd_warshall function
:return: source code
"""
return inspect.getsource(floyd_warshall)