Skip to content

Commit b2a6d2d

Browse files
committed
Implemented A* algorithm and updated test cases
1 parent ed88315 commit b2a6d2d

File tree

2 files changed

+107
-2
lines changed

2 files changed

+107
-2
lines changed

pydatastructs/graphs/algorithms.py

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,10 @@ def shortest_paths(graph: Graph, algorithm: str,
736736
({'V1': 0, 'V2': 11, 'V3': 21}, {'V1': None, 'V2': 'V1', 'V3': 'V2'})
737737
>>> shortest_paths(G, 'dijkstra', 'V1')
738738
({'V2': 11, 'V3': 21, 'V1': 0}, {'V1': None, 'V2': 'V1', 'V3': 'V2'})
739-
739+
>>> grid_graph = Graph(AdjacencyListGraphNode("0,0"), AdjacencyListGraphNode("1,1"))
740+
>>> grid_graph.add_edge('0,0', '1,1', 2)
741+
>>> shortest_paths(grid_graph, 'a_star_with_manhattan', '0,0', '1,1')
742+
(2, {'1,1': '0,0'})
740743
References
741744
==========
742745
@@ -811,6 +814,75 @@ def _dijkstra_adjacency_list(graph: Graph, start: str, target: str):
811814

812815
_dijkstra_adjacency_matrix = _dijkstra_adjacency_list
813816

817+
def _a_star_with_manhattan_adjacency_list(graph: Graph, start: str, target: str, **kwargs):
818+
"""
819+
A* algorithm with Manhattan distance as the heuristic function for grid-based graphs.
820+
"""
821+
def manhattan_distance(node1: str, node2: str) -> float:
822+
try:
823+
x1, y1 = map(int, node1.split(","))
824+
x2, y2 = map(int, node2.split(","))
825+
return abs(x1 - x2) + abs(y1 - y2)
826+
except (ValueError, TypeError):
827+
raise ValueError(f"Invalid node format. Expected 'x,y', got {node1} or {node2}")
828+
829+
# Validate inputs
830+
if start == target:
831+
return 0, {start: None}
832+
833+
if start not in graph.vertices or target not in graph.vertices:
834+
raise ValueError(f"Start or target node not in graph. Start: {start}, Target: {target}")
835+
836+
# Initialize data structures
837+
g_score = {v: float('inf') for v in graph.vertices}
838+
f_score = {v: float('inf') for v in graph.vertices}
839+
pred = {v: None for v in graph.vertices}
840+
visited = {v: False for v in graph.vertices}
841+
842+
# Initialize start node
843+
g_score[start] = 0
844+
f_score[start] = manhattan_distance(start, target)
845+
846+
# Priority queue for A* algorithm
847+
pq = PriorityQueue(implementation='binomial_heap')
848+
pq.push(start, f_score[start])
849+
850+
while not pq.is_empty:
851+
current = pq.pop()
852+
853+
# Goal reached
854+
if current == target:
855+
return g_score[target], pred
856+
857+
visited[current] = True
858+
859+
# Explore neighbors
860+
for neighbor in graph.neighbors(current):
861+
if visited[neighbor.name]:
862+
continue
863+
864+
edge = graph.get_edge(current, neighbor.name)
865+
if not edge:
866+
continue
867+
868+
# Calculate tentative g_score
869+
tentative_g_score = g_score[current] + edge.value
870+
871+
# Update if better path found
872+
if tentative_g_score < g_score[neighbor.name]:
873+
pred[neighbor.name] = current
874+
g_score[neighbor.name] = tentative_g_score
875+
f_score[neighbor.name] = (
876+
tentative_g_score +
877+
manhattan_distance(neighbor.name, target)
878+
)
879+
pq.push(neighbor.name, f_score[neighbor.name])
880+
881+
# No path exists
882+
raise ValueError(f"No path exists between {start} and {target}")
883+
884+
_a_star_with_manhattan_adjacency_matrix = _a_star_with_manhattan_adjacency_list
885+
814886
def all_pair_shortest_paths(graph: Graph, algorithm: str,
815887
**kwargs) -> tuple:
816888
"""

pydatastructs/graphs/tests/test_algorithms.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,38 @@ def _test_shortest_paths_positive_edges(ds, algorithm):
293293
graph.remove_edge('SLC', 'D')
294294
graph.add_edge('D', 'SLC', -10)
295295
assert raises(ValueError, lambda: shortest_paths(graph, 'bellman_ford', 'SLC'))
296-
296+
297+
def _test_a_star_manhattan(ds):
298+
import pydatastructs.utils.misc_util as utils
299+
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")
300+
vertices = [
301+
GraphNode("0,0"),
302+
GraphNode("1,1"),
303+
GraphNode("2,2")
304+
]
305+
graph = Graph(*vertices)
306+
graph.add_edge("0,0", "1,1", 2)
307+
graph.add_edge("1,1", "2,2", 3)
308+
309+
distance, pred = shortest_paths(graph, 'a_star_with_manhattan', "0,0", "2,2")
310+
assert distance == 5 # 2 + 3
311+
assert pred['2,2'] == '1,1'
312+
assert pred['1,1'] == '0,0'
313+
# No path scenario
314+
no_path_graph = Graph(
315+
GraphNode("0,0"),
316+
GraphNode("1,1"),
317+
GraphNode("2,2")
318+
)
319+
320+
with raises(ValueError, match="No path exists"):
321+
shortest_paths(no_path_graph, 'a_star_with_manhattan', "0,0", "2,2")
322+
# Same node scenario
323+
same_node_graph = Graph(GraphNode("1,1"))
324+
distance, pred = shortest_paths(same_node_graph, 'a_star_with_manhattan', "1,1", "1,1")
325+
326+
assert distance == 0
327+
assert pred == {'1,1': None}
297328
def _test_shortest_paths_negative_edges(ds, algorithm):
298329
import pydatastructs.utils.misc_util as utils
299330
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")
@@ -321,6 +352,8 @@ def _test_shortest_paths_negative_edges(ds, algorithm):
321352
_test_shortest_paths_negative_edges("Matrix", 'bellman_ford')
322353
_test_shortest_paths_positive_edges("List", 'dijkstra')
323354
_test_shortest_paths_positive_edges("Matrix", 'dijkstra')
355+
_test_a_star_manhattan("List")
356+
_test_a_star_manhattan("Matrix")
324357

325358
def test_all_pair_shortest_paths():
326359

0 commit comments

Comments
 (0)