Skip to content

Commit 1ba4628

Browse files
committed
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.
1 parent f4c1677 commit 1ba4628

File tree

4 files changed

+160
-3
lines changed

4 files changed

+160
-3
lines changed

docs/source/pydatastructs/graphs/algorithms.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ Algorithms
2020
.. autofunction:: pydatastructs.topological_sort
2121

2222
.. autofunction:: pydatastructs.topological_sort_parallel
23+
24+
.. autofunction:: pydatastructs.yen_algorithm

pydatastructs/graphs/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
all_pair_shortest_paths,
2222
topological_sort,
2323
topological_sort_parallel,
24-
max_flow
24+
max_flow,
25+
yen_algorithm
2526
)
2627

2728
__all__.extend(algorithms.__all__)

pydatastructs/graphs/algorithms.py

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from pydatastructs.graphs.graph import Graph
1212
from pydatastructs.linear_data_structures.algorithms import merge_sort_parallel
1313
from pydatastructs import PriorityQueue
14+
from copy import deepcopy
15+
import heapq
1416

1517
__all__ = [
1618
'breadth_first_search',
@@ -23,7 +25,8 @@
2325
'all_pair_shortest_paths',
2426
'topological_sort',
2527
'topological_sort_parallel',
26-
'max_flow'
28+
'max_flow',
29+
'yen_algorithm'
2730
]
2831

2932
Stack = Queue = deque
@@ -1209,3 +1212,116 @@ def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs):
12091212
f"Currently {algorithm} algorithm isn't implemented for "
12101213
"performing max flow on graphs.")
12111214
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

pydatastructs/graphs/tests/test_algorithms.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
breadth_first_search_parallel, minimum_spanning_tree,
33
minimum_spanning_tree_parallel, strongly_connected_components,
44
depth_first_search, shortest_paths,all_pair_shortest_paths, topological_sort,
5-
topological_sort_parallel, max_flow)
5+
topological_sort_parallel, max_flow, yen_algorithm)
66
from pydatastructs.utils.raises_util import raises
7+
import pytest
78

89
def test_breadth_first_search():
910

@@ -448,3 +449,40 @@ def _test_max_flow(ds, algorithm):
448449
_test_max_flow("Matrix", "edmonds_karp")
449450
_test_max_flow("List", "dinic")
450451
_test_max_flow("Matrix", "dinic")
452+
453+
def test_yen_algorithm():
454+
"""
455+
Test function for Yen's Algorithm to find K shortest paths.
456+
"""
457+
def _test_yen_algorithm(ds):
458+
import pydatastructs.utils.misc_util as utils
459+
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")
460+
461+
V1 = GraphNode("V1")
462+
V2 = GraphNode("V2")
463+
V3 = GraphNode("V3")
464+
V4 = GraphNode("V4")
465+
V5 = GraphNode("V5")
466+
467+
G = Graph(V1, V2, V3, V4, V5)
468+
469+
G.add_edge(V1.name, V2.name, 1)
470+
G.add_edge(V2.name, V3.name, 2)
471+
G.add_edge(V3.name, V4.name, 1)
472+
G.add_edge(V4.name, V5.name, 3)
473+
G.add_edge(V1.name, V3.name, 4)
474+
G.add_edge(V3.name, V5.name, 2)
475+
476+
k_shortest_paths = yen_algorithm(G, V1.name, V5.name, K=3)
477+
478+
expected_paths = [
479+
['V1', 'V2', 'V3', 'V5'],
480+
['V1', 'V3', 'V5'],
481+
['V1', 'V2', 'V3', 'V4', 'V5']
482+
]
483+
484+
assert len(k_shortest_paths) == 3, "Expected 3 shortest paths"
485+
for i, path in enumerate(k_shortest_paths):
486+
assert path == expected_paths[i], f"Path {i} does not match expected path. Got {path}, expected {expected_paths[i]}"
487+
488+
_test_yen_algorithm("List")

0 commit comments

Comments
 (0)