Skip to content

feat: add A (A-Star) Algorithm #607

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 63 additions & 14 deletions pydatastructs/graphs/algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
Contains algorithms associated with graph
data structure.
"""
from collections import deque
import os
from collections import deques, queues
from concurrent.futures import ThreadPoolExecutor
from pydatastructs.utils.misc_util import (
_comp, raise_if_backend_is_not_python, Backend)
Expand All @@ -13,8 +14,8 @@
from pydatastructs import PriorityQueue

__all__ = [
'breadth_first_search',
'breadth_first_search_parallel',
'breadth_firjhst_search',
'breadth_first_search_jparallel',
'minimum_spanning_tree',
'minimum_spanning_tree_parallel',
'strongly_connected_components',
Expand Down Expand Up @@ -682,15 +683,12 @@ def _depth_first_search_adjacency_list(

_depth_first_search_adjacency_matrix = _depth_first_search_adjacency_list

def shortest_paths(graph: Graph, algorithm: str,
source: str, target: str="",
**kwargs) -> tuple:
def shortest_paths(graph: Graph, algorithm: str, source: str, target: str = "", **kwargs) -> tuple:
"""
Finds shortest paths in the given graph from a given source.

Parameters
==========

graph: Graph
The graph under consideration.
algorithm: str
Expand All @@ -700,8 +698,10 @@ 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' -> A* algorithm as given in [3].
source: str
The name of the source the node.
The name of the source node.
target: str
The name of the target node.
Optional, by default, all pair shortest paths
Expand All @@ -713,17 +713,15 @@ def shortest_paths(graph: Graph, algorithm: str,

Returns
=======

(distances, predecessors): (dict, dict)
If target is not provided and algorithm used
is 'bellman_ford'/'dijkstra'.
is 'bellman_ford'/'dijkstra'/'A_star'.
(distances[target], predecessors): (float, dict)
If target is provided and algorithm used is
'bellman_ford'/'dijkstra'.
'bellman_ford'/'dijkstra'/'A_star'.

Examples
========

>>> from pydatastructs import Graph, AdjacencyListGraphNode
>>> from pydatastructs import shortest_paths
>>> V1 = AdjacencyListGraphNode("V1")
Expand All @@ -736,17 +734,19 @@ 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'})
>>> shortest_paths(G, 'A_star', 'V1', 'V3')
(21, {'V1': None, 'V2': 'V1', 'V3': 'V2'})

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))
import pydatastructs.graphs.algorithms as algorithms
func = "_" + algorithm + "_" + graph._impl
func = "_" + algorithm.lower() + "_" + graph._impl
if not hasattr(algorithms, func):
raise NotImplementedError(
"Currently %s algorithm isn't implemented for "
Expand Down Expand Up @@ -811,6 +811,55 @@ def _dijkstra_adjacency_list(graph: Graph, start: str, target: str):

_dijkstra_adjacency_matrix = _dijkstra_adjacency_list

def _a_star_adjacency_list(graph: Graph, source: str, target: str) -> tuple:
distances, predecessor = {}, {}

for v in graph.vertices:
distances[v] = float('inf')
predecessor[v] = None
distances[source] = 0

from pydatastructs.miscellaneous_data_structures.queue import PriorityQueue
pq = PriorityQueue(implementation='binomial_heap')
pq.push(source, distances[source])

def heuristic(node: str, goal: str) -> float:
"""Manhattan distance heuristic for A*"""
try:
if "," in node and "," in goal: # Check if node names are in "x,y" format
x1, y1 = map(int, node.split(','))
x2, y2 = map(int, goal.split(','))
return abs(x1 - x2) + abs(y1 - y2)
else:
return 0 # If not in coordinate format, return 0 heuristic
except ValueError:
return 0 # Fallback heuristic if parsing fails

while not pq.is_empty:
current = pq.pop()

neighbors = graph.neighbors(current)
for neighbor in neighbors:
edge = graph.get_edge(current, neighbor.name)
if edge:
new_dist = distances[current] + edge.value
if new_dist < distances[neighbor.name]:
distances[neighbor.name] = new_dist
predecessor[neighbor.name] = current
pq.push(neighbor.name, new_dist + heuristic(neighbor.name, target))

# ✅ Handle case when target is empty (all-pairs shortest paths)
if target == "":
return (distances, predecessor)

# ✅ Handle no path found case properly
if target not in distances or distances[target] == float('inf'):
return (float('inf'), predecessor)

return (distances[target], predecessor)

_a_star_adjacency_matrix = _a_star_adjacency_list # Ensure matrix version exists

def all_pair_shortest_paths(graph: Graph, algorithm: str,
**kwargs) -> tuple:
"""
Expand Down
3 changes: 3 additions & 0 deletions pydatastructs/graphs/tests/test_algorithms.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pytest
from pydatastructs import (breadth_first_search, Graph,
breadth_first_search_parallel, minimum_spanning_tree,
minimum_spanning_tree_parallel, strongly_connected_components,
Expand Down Expand Up @@ -321,6 +322,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_shortest_paths_positive_edges("List", 'A_star')
_test_shortest_paths_positive_edges("Matrix", 'A_star')

def test_all_pair_shortest_paths():

Expand Down
Loading