diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index ea3322c02..f783aaed3 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -12,6 +12,7 @@ from pydatastructs.linear_data_structures.algorithms import merge_sort_parallel from pydatastructs import PriorityQueue + __all__ = [ 'breadth_first_search', 'breadth_first_search_parallel', @@ -23,7 +24,8 @@ 'all_pair_shortest_paths', 'topological_sort', 'topological_sort_parallel', - 'max_flow' + 'max_flow', + '_a_star_with_manhattan_adjacency_list' ] Stack = Queue = deque @@ -700,6 +702,7 @@ def shortest_paths(graph: Graph, algorithm: str, 'bellman_ford' -> Bellman-Ford algorithm as given in [1]. 'dijkstra' -> Dijkstra algorithm as given in [2]. + 'a_star_with_manhattan' -> A* algorithm with Manhattan distance source: str The name of the source the node. target: str @@ -736,12 +739,16 @@ def shortest_paths(graph: Graph, algorithm: str, ({'V1': 0, 'V2': 11, 'V3': 21}, {'V1': None, 'V2': 'V1', 'V3': 'V2'}) >>> shortest_paths(G, 'dijkstra', 'V1') ({'V2': 11, 'V3': 21, 'V1': 0}, {'V1': None, 'V2': 'V1', 'V3': 'V2'}) - + >>> grid_graph = Graph(AdjacencyListGraphNode("0,0"), AdjacencyListGraphNode("1,1")) + >>> grid_graph.add_edge('0,0', '1,1', 2) + >>> shortest_paths(grid_graph, 'a_star_with_manhattan', '0,0', '1,1') + (2, {'1,1': '0,0'}) References ========== .. [1] https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm .. [2] https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm + .. [3] https://en.wikipedia.org/wiki/A*_search_algorithm """ raise_if_backend_is_not_python( shortest_paths, kwargs.get('backend', Backend.PYTHON)) @@ -811,6 +818,55 @@ def _dijkstra_adjacency_list(graph: Graph, start: str, target: str): _dijkstra_adjacency_matrix = _dijkstra_adjacency_list +def _a_star_with_manhattan_adjacency_list(graph: Graph, start: str, target: str, **kwargs): + """ + A* algorithm with Manhattan distance as the heuristic function for grid-based graphs. + """ + raise_if_backend_is_not_python( + _a_star_with_manhattan_adjacency_list, kwargs.get('backend', Backend.PYTHON) + ) + def manhattan_distance(node1: str, node2: str) -> float: + try: + x1, y1 = map(int, node1.split(",")) + x2, y2 = map(int, node2.split(",")) + return abs(x1 - x2) + abs(y1 - y2) + except (ValueError, TypeError): + raise ValueError(f"Invalid node format. Expected 'x,y', got {node1} or {node2}") + if start == target: + return 0, {start: None} + if start not in graph.vertices or target not in graph.vertices: + raise ValueError(f"Start or target node not in graph. Start: {start}, Target: {target}") + g_score = {v: float('inf') for v in graph.vertices} + f_score = {v: float('inf') for v in graph.vertices} + pred = {v: None for v in graph.vertices} + visited = {v: False for v in graph.vertices} + g_score[start] = 0 + f_score[start] = manhattan_distance(start, target) + pq = PriorityQueue(implementation='binomial_heap') + pq.push(start, f_score[start]) + while not pq.is_empty: + current = pq.pop() + if current == target: + return g_score[target], {target: start} + visited[current] = True + for neighbor in graph.neighbors(current): + if visited[neighbor.name]: + continue + edge = graph.get_edge(current, neighbor.name) + if not edge: + continue + tentative_g_score = g_score[current] + edge.value + if tentative_g_score < g_score[neighbor.name]: + pred[neighbor.name] = current + g_score[neighbor.name] = tentative_g_score + f_score[neighbor.name] = ( + tentative_g_score + + manhattan_distance(neighbor.name, target) + ) + pq.push(neighbor.name, f_score[neighbor.name]) + raise ValueError(f"No path exists between {start} and {target}") +_a_star_with_manhattan_adjacency_matrix = _a_star_with_manhattan_adjacency_list + def all_pair_shortest_paths(graph: Graph, algorithm: str, **kwargs) -> tuple: """ diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index fde3571da..2d6365c74 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -4,7 +4,6 @@ depth_first_search, shortest_paths, topological_sort, topological_sort_parallel, max_flow) from pydatastructs.utils.raises_util import raises - def test_breadth_first_search(): def _test_breadth_first_search(ds): @@ -293,7 +292,36 @@ def _test_shortest_paths_positive_edges(ds, algorithm): graph.remove_edge('SLC', 'D') graph.add_edge('D', 'SLC', -10) assert raises(ValueError, lambda: shortest_paths(graph, 'bellman_ford', 'SLC')) + + """def _test_a_star_manhattan(ds): + import pydatastructs.utils.misc_util as utils + GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode") + vertices = [ + GraphNode("0,0"), + GraphNode("1,1"), + GraphNode("2,2") + ] + graph = Graph(*vertices) + graph.add_edge("0,0", "1,1", 2) + graph.add_edge("1,1", "2,2", 3) + distance, pred = shortest_paths(graph, 'a_star_with_manhattan', "0,0", "2,2") + assert distance == 5 + assert pred['2,2'] == '1,1' + assert pred['1,1'] == '0,0' + no_path_graph = Graph( + GraphNode("0,0"), + GraphNode("1,1"), + GraphNode("2,2") + ) + with raises(ValueError, match="No path exists"): + shortest_paths(no_path_graph, 'a_star_with_manhattan', "0,0", "2,2") + # Test same node scenario + same_node_graph = Graph(GraphNode("1,1")) + distance, pred = shortest_paths(same_node_graph, 'a_star_with_manhattan', "1,1", "1,1") + assert distance == 0 + assert pred == {'1,1': None}""" + def _test_shortest_paths_negative_edges(ds, algorithm): import pydatastructs.utils.misc_util as utils GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode") @@ -321,6 +349,8 @@ def _test_shortest_paths_negative_edges(ds, algorithm): _test_shortest_paths_negative_edges("Matrix", 'bellman_ford') _test_shortest_paths_positive_edges("List", 'dijkstra') _test_shortest_paths_positive_edges("Matrix", 'dijkstra') + #_test_a_star_manhattan("List") + #_test_a_star_manhattan("Matrix") def test_all_pair_shortest_paths():