Lab 7: Heuristic Search Algorithms

Optimizing Paths with Intelligent Guesses

1. Best-First Search: The Nearest Apple

Problem Scenario:

Spider-Man needs to find the minimum number of steps to reach the nearest apple cart ('A') from his starting position ('S') on a city grid. The grid contains obstacles (buildings represented by '#'). He can move up, down, left, or right.

Sample Input


Grid:
[
  ['S', '.', '.', '#', '.', '.'],
  ['.', '#', '.', '#', '.', '#'],
  ['.', '#', '.', '.', '.', '.'],
  ['.', '.', '#', '#', '.', '.'],
  ['#', '.', '#', 'A', '.', '#']
]
                

Expected Output


Minimum steps required: 9
                

# Define a helper function to calculate Manhattan distance (our heuristic)
def manhattan_distance(p1, p2):
    # p1 and p2 are tuples like (row, col)
    # Calculate and return the absolute difference of coordinates

# Main search function
def best_first_search(grid):
    # Find start ('S') and apple ('A') positions
    
    # Initialize a priority queue to store (heuristic_cost, steps, current_position)
    # The priority queue will automatically sort by the first element (heuristic_cost)
    
    # Initialize a 'visited' set to keep track of visited cells
    
    # Add the starting position to the priority queue with 0 steps
    
    # Loop while the priority queue is not empty:
        # Get the cell with the smallest heuristic cost from the queue
        
        # If this cell is the apple, return the number of steps taken
        
        # If this cell has been visited, continue to the next iteration
        
        # Mark the current cell as visited
        
        # Explore neighbors (up, down, left, right):
            # For each neighbor, check if it's a valid move (within grid, not an obstacle)
            # If valid, calculate its heuristic cost to the apple
            # Add (heuristic_cost, new_steps, neighbor_position) to the priority queue
            
    # If the loop finishes and the apple wasn't found, return -1
                    

Example: Navigating from Bhagalpur to Patna

Find the most promising path from Bhagalpur to Patna using straight-line distance (heuristic) to guide the search.

Best-First Search is a **greedy algorithm**. It always chooses the next node that *appears* to be closest to the goal, based on the heuristic value. It doesn't consider the cost of the path taken so far.

  • Step 1: Start at Bhagalpur (Heuristic: 210km). Add its neighbors, Munger (H:160) and Banka (H:240), to a priority queue.
  • Step 2: The queue is now [(160, Munger), (240, Banka)]. The algorithm greedily chooses Munger because its heuristic is lower.
  • Step 3: From Munger, explore its neighbors (Lakhisarai, Begusarai). Lakhisarai (H:110) is more promising than Begusarai (H:125). The queue now contains [(110, Lakhisarai), (125, Begusarai), (240, Banka)].
  • Step 4: The algorithm again picks the best option: Lakhisarai (H:110). Its neighbor is Patna (H:0).
  • Step 5: Patna is the goal (H:0). The search stops and returns the path found.
Best-First Search from Bhagalpur to Patna Bhagalpur H=210 Banka H=240 Munger H=160 Begusarai H=125 Lakhisarai H=110 Patna H=0

import heapq

def solve_bihar_navigation_example():
    # Road connections between cities
    graph = {
        'Bhagalpur': ['Munger', 'Banka'],
        'Munger': ['Begusarai', 'Lakhisarai'],
        'Banka': ['Munger'],
        'Begusarai': ['Patna'],
        'Lakhisarai': ['Patna'],
        'Patna': []
    }
    
    # Heuristic: Approx. straight-line distance to Patna in km
    heuristics = {
        'Bhagalpur': 210, 'Munger': 160, 'Banka': 240,
        'Begusarai': 125, 'Lakhisarai': 110, 'Patna': 0
    }
    
    start = 'Bhagalpur'
    goal = 'Patna'

    pq = [(heuristics[start], [start])] # (heuristic, path)
    visited = set()

    while pq:
        h, path = heapq.heappop(pq)
        current_city = path[-1]

        if current_city == goal:
            return f"Path found: {' -> '.join(path)}"

        if current_city in visited:
            continue
        visited.add(current_city)
        
        for neighbor in graph[current_city]:
            if neighbor not in visited:
                new_path = path + [neighbor]
                heapq.heappush(pq, (heuristics[neighbor], new_path))
                
    return "Path not found"

# Expected Output: Path found: Bhagalpur -> Munger -> Lakhisarai -> Patna