Implemented A*

This commit is contained in:
Jan Mrna 2025-09-20 16:58:04 +02:00
parent 8981857ee7
commit 24c6ff2d06
2 changed files with 46 additions and 9 deletions

View File

@ -1,4 +1,4 @@
# LPP pathfinding task # Pathfinding demo
## TODO ## TODO
@ -7,11 +7,12 @@
- [x] drawing utility - [x] drawing utility
- [x] interface for pathfinding - [x] interface for pathfinding
- [x] research methods - [x] research methods
- [ ] implement methods - [x] implement methods
- [x] DFS - [x] DFS
- [x] BFS - [x] BFS
- [x] Dijsktra - [x] Dijsktra
- [ ] A* - [x] GBFS
- [x] A*
- [x] performance measurement: time/visited nodes - [x] performance measurement: time/visited nodes
- [ ] finish text on the page - [ ] finish text on the page
- [x] create a dedicated python script - [x] create a dedicated python script

View File

@ -268,6 +268,7 @@ class DijkstraAlgorithm(PathFinderBase):
""" """
Dijsktra's algorithm (Uniform Cost Search) Dijsktra's algorithm (Uniform Cost Search)
Like BFS, but takes into account cost of nodes Like BFS, but takes into account cost of nodes
(priority for the search being the distance from the start)
""" """
name = "Dijkstra's Algorithm" name = "Dijkstra's Algorithm"
@ -302,9 +303,8 @@ class DijkstraAlgorithm(PathFinderBase):
class GBFS(PathFinderBase): class GBFS(PathFinderBase):
""" """
Like Dijsktra's Algorithm, but uses some heuristic Like Dijsktra's Algorithm, but uses some heuristic as a priority
as a priority for the PriorityQueue instead of the cost of the node
Also doesn't care about the cost of the node
""" """
name = "Greedy Best First Search" name = "Greedy Best First Search"
@ -346,12 +346,48 @@ class GBFS(PathFinderBase):
class A_star(PathFinderBase): class A_star(PathFinderBase):
"""
Combines Dijsktra's Algorithm and GBFS:
priority is the sum of the heuristic and distance from the start
"""
name = "A*" name = "A*"
def _CalculatePath(self, start_point: Point2D, end_point: Point2D) -> Optional[Path]: @staticmethod
... def heuristic(a: Point2D, b: Point2D) -> float:
# for now we use Manhattan distance, although
# it is probably not entirely correct, given that
# we can also move diagonally in the grid
# TODO a problem for future me
x_a, y_a = a
x_b, y_b = b
return abs(x_a - x_b) + abs(y_a - y_b)
def _CalculatePath(self, start_point: Point2D, end_point: Point2D) -> Optional[Path]:
frontier: PriorityQueue[PrioritizedItem] = PriorityQueue()
came_from: dict[Point2D, Optional[Point2D]] = { end_point: None }
cost_so_far: dict[Point2D, float] = { end_point: 0.0 }
frontier.put(PrioritizedItem(end_point, 0.0))
while not frontier.empty():
current = frontier.get().item
if current == start_point:
# early exit
break
for next_point in self._map.GetNeighbours(current):
new_cost = cost_so_far[current] + self._map.Visit(next_point)
if next_point not in cost_so_far or new_cost < cost_so_far[next_point]:
cost_so_far[next_point] = new_cost
priority = new_cost + self.heuristic(start_point, next_point)
frontier.put(PrioritizedItem(next_point, priority))
came_from[next_point] = current
# create the actual path
path: Path = [start_point]
current = start_point
while came_from[current] is not None:
current = came_from[current]
path.append(current)
return path
# #
# Calculate paths using various methods and visualize them # Calculate paths using various methods and visualize them
@ -382,7 +418,7 @@ def main():
elapsed_time, visited_nodes = path_finder.GetStats() elapsed_time, visited_nodes = path_finder.GetStats()
if path is not None: if path is not None:
cost = m.GetPathCost(path) cost = m.GetPathCost(path)
print(f"{path_finder.name:22}: took {elapsed_time/1e6:.3f} ms, visited {visited_nodes} nodes, cost {cost:.2f}") print(f"{path_finder.name:24}: took {elapsed_time/1e6:.3f} ms, visited {visited_nodes} nodes, cost {cost:.2f}")
v.DrawPath(path) v.DrawPath(path)
else: else:
print(f"{path_finder.name}: No path found") print(f"{path_finder.name}: No path found")