|
11 | 11 | from pydatastructs.graphs.graph import Graph
|
12 | 12 | from pydatastructs.linear_data_structures.algorithms import merge_sort_parallel
|
13 | 13 | from pydatastructs import PriorityQueue
|
| 14 | +from copy import deepcopy |
| 15 | +import heapq |
14 | 16 |
|
15 | 17 | __all__ = [
|
16 | 18 | 'breadth_first_search',
|
|
23 | 25 | 'all_pair_shortest_paths',
|
24 | 26 | 'topological_sort',
|
25 | 27 | 'topological_sort_parallel',
|
26 |
| - 'max_flow' |
| 28 | + 'max_flow', |
| 29 | + 'yen_algorithm' |
27 | 30 | ]
|
28 | 31 |
|
29 | 32 | Stack = Queue = deque
|
@@ -1209,3 +1212,116 @@ def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs):
|
1209 | 1212 | f"Currently {algorithm} algorithm isn't implemented for "
|
1210 | 1213 | "performing max flow on graphs.")
|
1211 | 1214 | return getattr(algorithms, func)(graph, source, sink)
|
| 1215 | + |
| 1216 | +def yen_algorithm(graph, source, target, K, **kwargs): |
| 1217 | + """ |
| 1218 | + Finds the K shortest paths from source to target in a graph using Yen's algorithm. |
| 1219 | +
|
| 1220 | + Parameters |
| 1221 | + ========== |
| 1222 | + graph: Graph |
| 1223 | + The graph on which Yen's algorithm is to be performed. |
| 1224 | + source: str |
| 1225 | + The name of the source node. |
| 1226 | + target: str |
| 1227 | + The name of the target node. |
| 1228 | + K: int |
| 1229 | + The number of shortest paths to find. |
| 1230 | + backend: pydatastructs.Backend |
| 1231 | + The backend to be used. |
| 1232 | + Optional, by default, the best available backend is used. |
| 1233 | +
|
| 1234 | + Returns |
| 1235 | + ======= |
| 1236 | + list |
| 1237 | + A list of the K shortest paths, where each path is a list of node names. |
| 1238 | + """ |
| 1239 | + raise_if_backend_is_not_python( |
| 1240 | + yen_algorithm, kwargs.get('backend', Backend.PYTHON)) |
| 1241 | + |
| 1242 | + def dijkstra_shortest_path(graph, source, target): |
| 1243 | + """ |
| 1244 | + Helper function to find the shortest path using Dijkstra's algorithm. |
| 1245 | + """ |
| 1246 | + dist, pred = {}, {} |
| 1247 | + for v in graph.vertices: |
| 1248 | + dist[v] = float('inf') |
| 1249 | + pred[v] = None |
| 1250 | + dist[source] = 0 |
| 1251 | + pq = PriorityQueue(implementation='binomial_heap') |
| 1252 | + for vertex in dist: |
| 1253 | + pq.push(vertex, dist[vertex]) |
| 1254 | + while not pq.is_empty: |
| 1255 | + u = pq.pop() |
| 1256 | + if u == target: |
| 1257 | + break |
| 1258 | + for v in graph.neighbors(u): |
| 1259 | + edge_str = u + '_' + v.name |
| 1260 | + if edge_str in graph.edge_weights: |
| 1261 | + alt = dist[u] + graph.edge_weights[edge_str].value |
| 1262 | + if alt < dist[v.name]: |
| 1263 | + dist[v.name] = alt |
| 1264 | + pred[v.name] = u |
| 1265 | + pq.push(v.name, alt) |
| 1266 | + path = [] |
| 1267 | + if dist[target] != float('inf'): |
| 1268 | + current = target |
| 1269 | + while current is not None: |
| 1270 | + path.append(current) |
| 1271 | + current = pred[current] |
| 1272 | + path.reverse() |
| 1273 | + return path |
| 1274 | + |
| 1275 | + A = [] |
| 1276 | + B = [] |
| 1277 | + |
| 1278 | + shortest_path = dijkstra_shortest_path(graph, source, target) |
| 1279 | + if not shortest_path: |
| 1280 | + return A |
| 1281 | + A.append(shortest_path) |
| 1282 | + |
| 1283 | + for k in range(1, K): |
| 1284 | + for i in range(len(A[k-1]) - 1): |
| 1285 | + spur_node = A[k-1][i] |
| 1286 | + root_path = A[k-1][:i+1] |
| 1287 | + |
| 1288 | + edges_removed = [] |
| 1289 | + for path in A: |
| 1290 | + if len(path) > i and root_path == path[:i+1]: |
| 1291 | + u = path[i] |
| 1292 | + v = path[i+1] |
| 1293 | + edge_str = u + '_' + v |
| 1294 | + if edge_str in graph.edge_weights: |
| 1295 | + edges_removed.append((u, v, graph.edge_weights[edge_str].value)) |
| 1296 | + graph.remove_edge(u, v) |
| 1297 | + |
| 1298 | + nodes_removed = [] |
| 1299 | + for node_name in root_path[:-1]: |
| 1300 | + if node_name != spur_node: |
| 1301 | + node = graph.__getattribute__(node_name) |
| 1302 | + nodes_removed.append(node) |
| 1303 | + graph.remove_vertex(node_name) |
| 1304 | + |
| 1305 | + spur_path = dijkstra_shortest_path(graph, spur_node, target) |
| 1306 | + |
| 1307 | + if spur_path: |
| 1308 | + total_path = root_path[:-1] + spur_path |
| 1309 | + total_cost = sum(graph.edge_weights[total_path[i] + '_' + total_path[i+1]].value |
| 1310 | + for i in range(len(total_path)-1)) |
| 1311 | + B.append((total_cost, total_path)) |
| 1312 | + |
| 1313 | + for u, v, w in edges_removed: |
| 1314 | + graph.add_edge(u, v, w) |
| 1315 | + for node in nodes_removed: |
| 1316 | + graph.add_vertex(node) |
| 1317 | + |
| 1318 | + if not B: |
| 1319 | + break |
| 1320 | + |
| 1321 | + B.sort(key=lambda x: x[0]) |
| 1322 | + |
| 1323 | + shortest_candidate = B.pop(0)[1] |
| 1324 | + if shortest_candidate not in A: |
| 1325 | + A.append(shortest_candidate) |
| 1326 | + |
| 1327 | + return A |
0 commit comments