From 1ba462840739822a126a1bdc92c3ea88ad7e38e0 Mon Sep 17 00:00:00 2001 From: asmit27rai Date: Tue, 11 Mar 2025 05:24:20 +0530 Subject: [PATCH 1/2] feat: Implement Yen's Algorithm for K-Shortest Paths - Added Yen's Algorithm implementation to find the K shortest paths between two nodes in a graph. - Supports applications in network routing, transportation planning, and logistics. - Includes test cases to validate the correctness of the implementation. - Optimized for performance and usability within the graph algorithms module. --- .../pydatastructs/graphs/algorithms.rst | 2 + pydatastructs/graphs/__init__.py | 3 +- pydatastructs/graphs/algorithms.py | 118 +++++++++++++++++- pydatastructs/graphs/tests/test_algorithms.py | 40 +++++- 4 files changed, 160 insertions(+), 3 deletions(-) diff --git a/docs/source/pydatastructs/graphs/algorithms.rst b/docs/source/pydatastructs/graphs/algorithms.rst index 1749e3a4c..cfc701fa2 100644 --- a/docs/source/pydatastructs/graphs/algorithms.rst +++ b/docs/source/pydatastructs/graphs/algorithms.rst @@ -20,3 +20,5 @@ Algorithms .. autofunction:: pydatastructs.topological_sort .. autofunction:: pydatastructs.topological_sort_parallel + +.. autofunction:: pydatastructs.yen_algorithm diff --git a/pydatastructs/graphs/__init__.py b/pydatastructs/graphs/__init__.py index 9c00ca0aa..f0c48f988 100644 --- a/pydatastructs/graphs/__init__.py +++ b/pydatastructs/graphs/__init__.py @@ -21,7 +21,8 @@ all_pair_shortest_paths, topological_sort, topological_sort_parallel, - max_flow + max_flow, + yen_algorithm ) __all__.extend(algorithms.__all__) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 204c2fbd8..d8b3b2cd6 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -11,6 +11,8 @@ from pydatastructs.graphs.graph import Graph from pydatastructs.linear_data_structures.algorithms import merge_sort_parallel from pydatastructs import PriorityQueue +from copy import deepcopy +import heapq __all__ = [ 'breadth_first_search', @@ -23,7 +25,8 @@ 'all_pair_shortest_paths', 'topological_sort', 'topological_sort_parallel', - 'max_flow' + 'max_flow', + 'yen_algorithm' ] Stack = Queue = deque @@ -1209,3 +1212,116 @@ def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs): f"Currently {algorithm} algorithm isn't implemented for " "performing max flow on graphs.") return getattr(algorithms, func)(graph, source, sink) + +def yen_algorithm(graph, source, target, K, **kwargs): + """ + Finds the K shortest paths from source to target in a graph using Yen's algorithm. + + Parameters + ========== + graph: Graph + The graph on which Yen's algorithm is to be performed. + source: str + The name of the source node. + target: str + The name of the target node. + K: int + The number of shortest paths to find. + backend: pydatastructs.Backend + The backend to be used. + Optional, by default, the best available backend is used. + + Returns + ======= + list + A list of the K shortest paths, where each path is a list of node names. + """ + raise_if_backend_is_not_python( + yen_algorithm, kwargs.get('backend', Backend.PYTHON)) + + def dijkstra_shortest_path(graph, source, target): + """ + Helper function to find the shortest path using Dijkstra's algorithm. + """ + dist, pred = {}, {} + for v in graph.vertices: + dist[v] = float('inf') + pred[v] = None + dist[source] = 0 + pq = PriorityQueue(implementation='binomial_heap') + for vertex in dist: + pq.push(vertex, dist[vertex]) + while not pq.is_empty: + u = pq.pop() + if u == target: + break + for v in graph.neighbors(u): + edge_str = u + '_' + v.name + if edge_str in graph.edge_weights: + alt = dist[u] + graph.edge_weights[edge_str].value + if alt < dist[v.name]: + dist[v.name] = alt + pred[v.name] = u + pq.push(v.name, alt) + path = [] + if dist[target] != float('inf'): + current = target + while current is not None: + path.append(current) + current = pred[current] + path.reverse() + return path + + A = [] + B = [] + + shortest_path = dijkstra_shortest_path(graph, source, target) + if not shortest_path: + return A + A.append(shortest_path) + + for k in range(1, K): + for i in range(len(A[k-1]) - 1): + spur_node = A[k-1][i] + root_path = A[k-1][:i+1] + + edges_removed = [] + for path in A: + if len(path) > i and root_path == path[:i+1]: + u = path[i] + v = path[i+1] + edge_str = u + '_' + v + if edge_str in graph.edge_weights: + edges_removed.append((u, v, graph.edge_weights[edge_str].value)) + graph.remove_edge(u, v) + + nodes_removed = [] + for node_name in root_path[:-1]: + if node_name != spur_node: + node = graph.__getattribute__(node_name) + nodes_removed.append(node) + graph.remove_vertex(node_name) + + spur_path = dijkstra_shortest_path(graph, spur_node, target) + + if spur_path: + total_path = root_path[:-1] + spur_path + total_cost = sum(graph.edge_weights[total_path[i] + '_' + total_path[i+1]].value + for i in range(len(total_path)-1)) + B.append((total_cost, total_path)) + + for u, v, w in edges_removed: + graph.add_edge(u, v, w) + for node in nodes_removed: + graph.add_vertex(node) + + if not B: + break + + B.sort(key=lambda x: x[0]) + + shortest_candidate = B.pop(0)[1] + if shortest_candidate not in A: + A.append(shortest_candidate) + + return A diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index f1586f512..09c0c5121 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -2,8 +2,9 @@ breadth_first_search_parallel, minimum_spanning_tree, minimum_spanning_tree_parallel, strongly_connected_components, depth_first_search, shortest_paths,all_pair_shortest_paths, topological_sort, -topological_sort_parallel, max_flow) +topological_sort_parallel, max_flow, yen_algorithm) from pydatastructs.utils.raises_util import raises +import pytest def test_breadth_first_search(): @@ -448,3 +449,40 @@ def _test_max_flow(ds, algorithm): _test_max_flow("Matrix", "edmonds_karp") _test_max_flow("List", "dinic") _test_max_flow("Matrix", "dinic") + +def test_yen_algorithm(): + """ + Test function for Yen's Algorithm to find K shortest paths. + """ + def _test_yen_algorithm(ds): + import pydatastructs.utils.misc_util as utils + GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode") + + V1 = GraphNode("V1") + V2 = GraphNode("V2") + V3 = GraphNode("V3") + V4 = GraphNode("V4") + V5 = GraphNode("V5") + + G = Graph(V1, V2, V3, V4, V5) + + G.add_edge(V1.name, V2.name, 1) + G.add_edge(V2.name, V3.name, 2) + G.add_edge(V3.name, V4.name, 1) + G.add_edge(V4.name, V5.name, 3) + G.add_edge(V1.name, V3.name, 4) + G.add_edge(V3.name, V5.name, 2) + + k_shortest_paths = yen_algorithm(G, V1.name, V5.name, K=3) + + expected_paths = [ + ['V1', 'V2', 'V3', 'V5'], + ['V1', 'V3', 'V5'], + ['V1', 'V2', 'V3', 'V4', 'V5'] + ] + + assert len(k_shortest_paths) == 3, "Expected 3 shortest paths" + for i, path in enumerate(k_shortest_paths): + assert path == expected_paths[i], f"Path {i} does not match expected path. Got {path}, expected {expected_paths[i]}" + + _test_yen_algorithm("List") From c580669b21cd4f75730bf73ac0d9ef23955a98d1 Mon Sep 17 00:00:00 2001 From: asmit27rai Date: Wed, 12 Mar 2025 15:58:44 +0530 Subject: [PATCH 2/2] FIX --- pydatastructs/graphs/algorithms.py | 2 -- pydatastructs/graphs/tests/test_algorithms.py | 1 - 2 files changed, 3 deletions(-) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index e3840764a..b3dfa1df1 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -11,8 +11,6 @@ from pydatastructs.graphs.graph import Graph from pydatastructs.linear_data_structures.algorithms import merge_sort_parallel from pydatastructs import PriorityQueue -from copy import deepcopy -import heapq __all__ = [ 'breadth_first_search', diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index 09c0c5121..d5d9bbfed 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -4,7 +4,6 @@ depth_first_search, shortest_paths,all_pair_shortest_paths, topological_sort, topological_sort_parallel, max_flow, yen_algorithm) from pydatastructs.utils.raises_util import raises -import pytest def test_breadth_first_search():