Skip to content

Commit 5796161

Browse files
Merge pull request #394 from lck6055/updated-branch
Added A* Algo in Python
2 parents 15adfb4 + 9fd1b95 commit 5796161

1 file changed

Lines changed: 122 additions & 0 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
"""
2+
Algorithm: A* (A-star) Search
3+
Description: Finds the shortest path between a start node and a goal node using
4+
g(n) (actual cost so far) + h(n) (heuristic).
5+
Time Complexity: O(E) in worst case
6+
Space Complexity: O(V)
7+
Author: Kashyap
8+
Date: 2025-10-04
9+
"""
10+
11+
import heapq
12+
13+
class Node:
14+
def __init__(self, position, parent=None, g=0, h=0):
15+
self.position = position # (row, col)
16+
self.parent = parent # previous node in path
17+
self.g = g # cost from start
18+
self.h = h # heuristic (to goal)
19+
self.f = g + h # total cost
20+
21+
def __lt__(self, other):
22+
return self.f < other.f # needed for heapq priority
23+
24+
def heuristic(a, b):
25+
"""Manhattan distance heuristic"""
26+
return abs(a[0] - b[0]) + abs(a[1] - b[1])
27+
28+
def a_star(grid, start, goal):
29+
"""
30+
Perform A* search on a grid.
31+
32+
Args:
33+
grid (list[list[int]]): 0 = free cell, 1 = obstacle
34+
start (tuple): (row, col) start position
35+
goal (tuple): (row, col) goal position
36+
37+
Returns:
38+
path (list[tuple]): shortest path from start to goal
39+
"""
40+
open_set = []
41+
heapq.heappush(open_set, Node(start, None, 0, heuristic(start, goal)))
42+
visited = set()
43+
44+
while open_set:
45+
current = heapq.heappop(open_set)
46+
47+
if current.position == goal:
48+
# Reconstruct path
49+
path = []
50+
while current:
51+
path.append(current.position)
52+
current = current.parent
53+
return path[::-1] # reverse path
54+
55+
visited.add(current.position)
56+
57+
# Explore neighbors (up, down, left, right)
58+
directions = [(0,1), (0,-1), (1,0), (-1,0)]
59+
for d in directions:
60+
neighbor = (current.position[0] + d[0], current.position[1] + d[1])
61+
62+
# Skip if out of bounds or obstacle
63+
if (0 <= neighbor[0] < len(grid) and
64+
0 <= neighbor[1] < len(grid[0]) and
65+
grid[neighbor[0]][neighbor[1]] == 0 and
66+
neighbor not in visited):
67+
68+
g_cost = current.g + 1
69+
h_cost = heuristic(neighbor, goal)
70+
heapq.heappush(open_set, Node(neighbor, current, g_cost, h_cost))
71+
72+
return None # no path found
73+
74+
75+
# main func()
76+
if __name__ == "__main__":
77+
# Test Case 1: Normal case
78+
grid1 = [
79+
[0, 1, 0, 0, 0],
80+
[0, 1, 0, 1, 0],
81+
[0, 0, 0, 1, 0],
82+
[0, 1, 0, 0, 0],
83+
[0, 0, 0, 1, 0]
84+
]
85+
start1 = (0, 0)
86+
goal1 = (4, 4)
87+
path1 = a_star(grid1, start1, goal1)
88+
print("Test Case 1 Path:", path1) # Expected: a valid path like [(0,0),(1,0),(2,0),(2,1),...(4,4)]
89+
90+
# Test Case 2: Blocked path
91+
grid2 = [
92+
[0, 1, 1],
93+
[1, 1, 1],
94+
[0, 0, 0]
95+
]
96+
start2 = (0, 0)
97+
goal2 = (2, 2)
98+
path2 = a_star(grid2, start2, goal2)
99+
print("Test Case 2 Path:", path2) # Expected: None (no path exists)
100+
101+
# Test Case 3: Start = Goal
102+
grid3 = [
103+
[0, 0],
104+
[0, 0]
105+
]
106+
start3 = (0, 0)
107+
goal3 = (0, 0)
108+
path3 = a_star(grid3, start3, goal3)
109+
print("Test Case 3 Path:", path3) # Expected: [(0, 0)] (trivial path)
110+
111+
# Test Case 4: Larger grid with obstacles
112+
grid4 = [
113+
[0, 0, 0, 0, 0, 0],
114+
[1, 1, 1, 1, 1, 0],
115+
[0, 0, 0, 0, 0, 0],
116+
[0, 1, 1, 1, 1, 1],
117+
[0, 0, 0, 0, 0, 0]
118+
]
119+
start4 = (0, 0)
120+
goal4 = (4, 5)
121+
path4 = a_star(grid4, start4, goal4)
122+
print("Test Case 4 Path:", path4) # Expected: a valid path from top-left to bottom-right

0 commit comments

Comments
 (0)