Categories
English Articles

A* Search Algorithm and the maze traversal

The A* Search Algorithm is an graph traversal technique for efficiently finding the path, used in various applications like robotics. This article shares details and codes to navigate a maze.

After introducing graphs and shortest path techniques like Dijkstra’s and Bellman-Ford’s algorithms, this article will introduce the following method: A* Search Algorithm.

The original article is available on Medium, and the Thai version of this article is available here.

Table of Contents

A* Search Algorithm

A* Search Algorithm [1] is a basis of Artificial Intelligence for finding an appropriate path between the start and the destination in a graph. This technique is suitable for a directed unweighted graph with non-negative edge weights.

This technique is similar to Dijkstra’s Algorithm. However, there is a little difference.

A* Search Algorithm uses an evaluation function [2]:

f(x) = g(x) + h(x)

where

  • g(x) is the actual cost of the node (or the distance to get to the node in the graph from a starting point)
  • h(x) is the heuristic cost to reach a goal (or the estimation of the distance to get to the finish line from a node)
  • and f(x) is a priority that combines between g(x) and h(x). f(x) considers whether that node is suitable for passing.

Return to Table of Contents ↑


Process

A* search algorithm finds the appropriate path to traverse between the start (S) and the destination (G) by using the lowest cost. This technique requires two arrays:

  • Open list or priority queue is an array that stores nodes with the lowest estimated cost (from f(x)) and waits for evaluation.
  • Closed list is an array that stores evaluated nodes.

After defining an array, we use the open list to store the node with the lowest cost at the top and the highest cost at the bottom. This data structure is similar to a Min-Heap tree.

We summarize the process below.

Initialization

  1. Initialize the open and the closed lists.
  2. The open list initially contains only the start node as the root, while the closed list is empty.
  3. Set the cost of reaching the start node (or g(x)) to zero, get the heuristic cost (or h(x)), and calculate the actual cost (or f(x)).

The Loop

  1. Remove the root and place the node on the closed list.
  2. Search the neighbor nodes and loop inside to calculate the cost (or f(x)) to get the lowest one, which is a promising candidate for exploration.
  3. The node with the lowest cost is inserted into the open list and arranged to conform to the Min-Heap tree.
  4. Repeat the process until reaching the destination (G).

Return to Table of Contents ↑


Use cases

Various tasks use an A* Search Algorithm: Robotics, Video Games, Route Planning, Logistics, and Artificial Intelligence [3].

  • Robotics: A* algorithm assists the robot in navigating obstacles and finding an alternative path.
  • Video Games: This algorithm plans NPCs (or Non-Player Characters) to navigate inside the game smartly.
  • Route Planning: This technique provides the shortest path.
  • Logistics: This task uses this technique to plan vehicle routes and schedules.
  • Artificial Intelligence: This technique is utilized for natural language processing and machine learning to optimize decision-making processes.

Return to Table of Contents ↑


Computational Complexity

Time Complexity equals O(b^d), where b is a branching factor that finds the average line in each node, and d is the number of nodes on the result path.

Space Complexity equals O(b^d).

Return to Table of Contents ↑


Traverse a maze

We want to travel in the maze shown in the picture below, with a defined starting point and a destination. Then, we want to use the computer to find the most appropriate path.

The example maze (the black is a walkable area, while the white is not).

We can apply the A* search algorithm with the following processes.

Initialization

First, we create the maze variable as a class that contains

  • walkability (1 = walkable, 0 = non-walkable)
  • x and y coordinates
  • parent coordinates
  • heuristic costs (h(x))
  • actual costs (g(x))
  • and priorities (f(x)).

Then, we initialize the open and the closed set. The open set initially contains only the start node (at x = 0 and y = 0) as the root, while the closed set is empty.

After that, we set the costs (g(x), h(x), and f(x)) to zero.

The Loop

First, we remove the root and place the node in the closed list.

Second, we search the neighbor nodes from eight directions (Up, Down, Left, Right, Up-Left, Up-Right, Down-Left, and Down-Right). We consider three points.

  • Consider whether the neighbor nodes are walkable (or walkability = 0).
  • Check whether those nodes are in the closed list.
  • Calculate f(x) to find the lowest cost by h(x) = current h(x) + 1, g(x) = Euclidian distance* from the current to the neighbor node, and f(x) = h(x) + g(x).

*Euclidian Distance [3] is the most common distance measure to find the length of a segment connecting two points.

Euclidian Distance

Third, we insert the nodes with the lowest cost into the open list and arrange the list to conform to the Min-Heap Tree.

Fourth, loop the process from the first to the third until reaching the maze destination.

The Code

First, we import the essential libraries.

import heapq, copy, math

Next, we define a maze and a related class using the code below.

class Cell:
    def __init__(self, x, y, walkable):
        self.walkable = walkable      # Walkability
        self.x = x                    # X coordinate
        self.y = y                    # Y coordinate
        self.parent = [None, None]    # Parent coordinate
        self.h = float('inf')         # h(x)
        self.g = float('inf')         # g(x)
        self.f = float('inf')         # f(x)

maze = [
        ['1', '0', '0', '0', '0', '0', '0'],
        ['1', '1', '1', '1', '1', '1', '0'],
        ['1', '1', '0', '0', '0', '1', '0'],
        ['0', '1', '1', '0', '0', '0', '0'],
        ['0', '0', '1', '1', '1', '1', '0'],
        ['1', '1', '1', '1', '0', '1', '0'],
        ['1', '0', '0', '1', '0', '1', 'F'],
]

size_y = len(maze)
size_x = len(maze[0])

Third, we write a function to traverse a maze using an A* search algorithm, like the code below.

def isvalid(x, y, size_x, size_y):
    return x >= 0 and x < size_x and y >= 0 and y < size_y

# Calculate_h by Euclidian Distance
def calculate_h(source_x, source_y, dest_x, dest_y):
    return math.sqrt((source_x - dest_x) ** 2 +
                     (source_y - dest_y) ** 2)

def maze_astar(maze_orig, source_x = 0, source_y = 0, 
               dest_x = 8, dest_y = 8, size_x = 4, size_y = 4):
    if source_y == dest_y and source_x == dest_x:
        print("This is a destination.")
        return [[source_x, source_y]]
    
    # Create Maze, Open and Closed List
    maze = [[Cell(j, i, maze_orig[i][j]) for j in range(size_x)] for i in range(size_y)]
    open = []
    heapq.heappush(open, (0.0, source_x, source_y))
    close = [[False for j in range(size_x)] for i in range(size_y)]

    # Direction
    direction = [
        ('U', 0, -1),
        ('D', 0, 1),
        ('F', 1, 0),
        ('B', -1, 0),
        ('UF', 1, -1),
        ('UB', -1, -1),
        ('DF', 1, 1),
        ('DB', -1, 1)
    ]

    # Initial
    maze[source_y][source_x].parent = None
    maze[source_y][source_x].f = 0
    maze[source_y][source_x].g = 0
    maze[source_y][source_x].h = 0

    found_dest = False

    while len(open) > 0:
        q = heapq.heappop(open)
        source_x, source_y = q[1:]
        close[source_y][source_x] = True
        
        # Check if we found the destination.
        if source_y == dest_y and source_x == dest_x:
            print("We found the destination")
            found_dest = True

            # Trace back
            dest_vertex = [source_x, source_y]
            path = []
            while True:
                path.append(dest_vertex)
                dest_vertex = maze[dest_vertex[1]][dest_vertex[0]].parent
                if dest_vertex is None:
                    break

            path.reverse()
            return path
        
        # Find Direction
        for name, add_x, add_y in direction:
            # New Coordinates
            neighbor_x, neighbor_y = source_x + add_x, source_y + add_y

            # Is not valid continue
            if not isvalid(neighbor_x, neighbor_y, size_x, size_y) or \
               maze[neighbor_y][neighbor_x].walkable == '0' or \
               close[neighbor_y][neighbor_x]:
               continue

            new_g = maze[source_y][source_x].g + 1
            new_h = calculate_h(source_x, source_y, neighbor_x, neighbor_y)
            new_f = new_h + new_g
            if maze[neighbor_y][neighbor_x].f == float('inf') or \
               new_f < maze[neighbor_y][neighbor_x].f:
                maze[neighbor_y][neighbor_x].parent = [source_x, source_y]
                maze[neighbor_y][neighbor_x].f = new_f 
                maze[neighbor_y][neighbor_x].g = new_h 
                maze[neighbor_y][neighbor_x].h = new_g

                heapq.heappush(open, (new_f, neighbor_x, neighbor_y))

    if not found_dest:
        print("We cannot found dest.")
        return []
    
    return None

Final, we write the code to let the algorithm traverse a maze.

output = maze_astar(maze, 0, 0, 
         size_x - 1, size_y - 1, 
         size_x, size_y)

visualize_maze = copy.deepcopy(maze)
for x, y in output:
    visualize_maze[y][x] = 'X'

for i in range(size_y):
    for j in range(size_x):
        print(visualize_maze[i][j], end = '')
    print('')

The result is provided in the picture below. The blue one is an appropriate path provided by the algorithm from the start to the destination.

Return to Table of Contents ↑


Summary

A* Search algorithm is a graph traversal technique for finding the most appropriate path between the start and the destination in a graph. This technique is popular in various tasks, such as Robotics, Video Games, Route Planning, Logistics, and Artificial Intelligence.

In this article, we apply the algorithm for route planning to traverse a maze to get the most appropriate path to walk from the start to the destination.

If you found this content useful, press Clap, and share it to various social media platforms. Moreover, readers can follow me on Linkedin or X (or Twitter).

Return to Table of Contents ↑


Reference

  1. https://www.codecademy.com/resources/docs/ai/search-algorithms/a-star-search
  2. https://www.simplilearn.com/tutorials/artificial-intelligence-tutorial/a-star-algorithm
  3. https://towardsdatascience.com/9-distance-measures-in-data-science-918109d069fa

Return to Table of Contents ↑

By Kittisak Chotikkakamthorn

อดีตนักศึกษาฝึกงานทางด้าน AI ที่ภาควิชาวิศวกรรมไฟฟ้า มหาวิทยาลัย National Chung Cheng ที่ไต้หวัน ที่กำลังหางานทางด้าน Data Engineer ที่มีความสนใจทางด้าน Data, Coding และ Blogging / ติดต่อได้ที่: contact [at] nickuntitled.com

Exit mobile version