Skip to content

Commit acf466e

Browse files
committed
added a star algorithem and basic test cases
1 parent ed88315 commit acf466e

File tree

2 files changed

+89
-2
lines changed

2 files changed

+89
-2
lines changed

pydatastructs/graphs/algorithms.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
'topological_sort',
2525
'topological_sort_parallel',
2626
'max_flow'
27+
'a_star_with_manhattan'
2728
]
2829

2930
Stack = Queue = deque
@@ -700,6 +701,7 @@ def shortest_paths(graph: Graph, algorithm: str,
700701
'bellman_ford' -> Bellman-Ford algorithm as given in [1].
701702
702703
'dijkstra' -> Dijkstra algorithm as given in [2].
704+
'a_star_with_manhattan' -> A* algorithm with Manhattan distance
703705
source: str
704706
The name of the source the node.
705707
target: str
@@ -736,16 +738,27 @@ def shortest_paths(graph: Graph, algorithm: str,
736738
({'V1': 0, 'V2': 11, 'V3': 21}, {'V1': None, 'V2': 'V1', 'V3': 'V2'})
737739
>>> shortest_paths(G, 'dijkstra', 'V1')
738740
({'V2': 11, 'V3': 21, 'V1': 0}, {'V1': None, 'V2': 'V1', 'V3': 'V2'})
741+
>>> grid_graph = Graph(AdjacencyListGraphNode("0,0"), AdjacencyListGraphNode("1,1"))
742+
>>> grid_graph.add_edge('0,0', '1,1', 2)
743+
>>> shortest_paths(grid_graph, 'a_star_with_manhattan', '0,0', '1,1')
744+
(2, {'1,1': '0,0'})
739745
740746
References
741747
==========
742748
743749
.. [1] https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm
744750
.. [2] https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
751+
.. [3] https://en.wikipedia.org/wiki/A*_search_algorithm
745752
"""
746753
raise_if_backend_is_not_python(
747754
shortest_paths, kwargs.get('backend', Backend.PYTHON))
748755
import pydatastructs.graphs.algorithms as algorithms
756+
if algorithm == 'a_star_with_manhattan':
757+
if not target:
758+
raise ValueError("Target must be specified for A* algorithm")
759+
760+
func = "_a_star_with_manhattan_adjacency_list"
761+
return getattr(algorithms, func)(graph, source, target)
749762
func = "_" + algorithm + "_" + graph._impl
750763
if not hasattr(algorithms, func):
751764
raise NotImplementedError(
@@ -811,6 +824,51 @@ def _dijkstra_adjacency_list(graph: Graph, start: str, target: str):
811824

812825
_dijkstra_adjacency_matrix = _dijkstra_adjacency_list
813826

827+
def _a_star_with_manhattan_adjacency_list(graph: Graph, start: str, target: str, **kwargs):
828+
"""
829+
A* algorithm with Manhattan distance as the heuristic function for grid-based graphs.
830+
"""
831+
def manhattan_distance(node1: str, node2: str) -> float:
832+
try:
833+
x1, y1 = map(int, node1.split(","))
834+
x2, y2 = map(int, node2.split(","))
835+
return abs(x1 - x2) + abs(y1 - y2)
836+
except (ValueError, TypeError):
837+
raise ValueError(f"Invalid node format. Expected 'x,y', got {node1} or {node2}")
838+
if start == target:
839+
return 0, {start: None}
840+
if start not in graph.vertices or target not in graph.vertices:
841+
raise ValueError(f"Start or target node not in graph. Start: {start}, Target: {target}")
842+
g_score = {v: float('inf') for v in graph.vertices}
843+
f_score = {v: float('inf') for v in graph.vertices}
844+
pred = {v: None for v in graph.vertices}
845+
visited = {v: False for v in graph.vertices}
846+
g_score[start] = 0
847+
f_score[start] = manhattan_distance(start, target)
848+
pq = PriorityQueue(implementation='binomial_heap')
849+
pq.push(start, f_score[start])
850+
while not pq.is_empty:
851+
current = pq.pop()
852+
if current == target:
853+
return g_score[target], {target: start}
854+
visited[current] = True
855+
for neighbor in graph.neighbors(current):
856+
if visited[neighbor.name]:
857+
continue
858+
edge = graph.get_edge(current, neighbor.name)
859+
if not edge:
860+
continue
861+
tentative_g_score = g_score[current] + edge.value
862+
if tentative_g_score < g_score[neighbor.name]:
863+
pred[neighbor.name] = current
864+
g_score[neighbor.name] = tentative_g_score
865+
f_score[neighbor.name] = (
866+
tentative_g_score +
867+
manhattan_distance(neighbor.name, target)
868+
)
869+
pq.push(neighbor.name, f_score[neighbor.name])
870+
raise ValueError(f"No path exists between {start} and {target}")
871+
_a_star_with_manhattan_adjacency_matrix = _a_star_with_manhattan_adjacency_list
814872
def all_pair_shortest_paths(graph: Graph, algorithm: str,
815873
**kwargs) -> tuple:
816874
"""

pydatastructs/graphs/tests/test_algorithms.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,35 @@ 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+
distance, pred = shortest_paths(graph, 'a_star_with_manhattan', "0,0", "2,2")
309+
assert distance == 5
310+
assert pred['2,2'] == '1,1'
311+
assert pred['1,1'] == '0,0'
312+
no_path_graph = Graph(
313+
GraphNode("0,0"),
314+
GraphNode("1,1"),
315+
GraphNode("2,2")
316+
)
317+
with raises(ValueError, match="No path exists"):
318+
shortest_paths(no_path_graph, 'a_star_with_manhattan', "0,0", "2,2")
319+
# Test same node scenario
320+
same_node_graph = Graph(GraphNode("1,1"))
321+
distance, pred = shortest_paths(same_node_graph, 'a_star_with_manhattan', "1,1", "1,1")
322+
assert distance == 0
323+
assert pred == {'1,1': None}
324+
297325
def _test_shortest_paths_negative_edges(ds, algorithm):
298326
import pydatastructs.utils.misc_util as utils
299327
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")
@@ -321,7 +349,8 @@ def _test_shortest_paths_negative_edges(ds, algorithm):
321349
_test_shortest_paths_negative_edges("Matrix", 'bellman_ford')
322350
_test_shortest_paths_positive_edges("List", 'dijkstra')
323351
_test_shortest_paths_positive_edges("Matrix", 'dijkstra')
324-
352+
_test_a_star_manhattan("List")
353+
_test_a_star_manhattan("Matrix")
325354
def test_all_pair_shortest_paths():
326355

327356
def _test_shortest_paths_negative_edges(ds, algorithm):

0 commit comments

Comments
 (0)