While blind search techniques like Breadth-First and Depth-First Search rely purely on basic queue structures and problem definitions to traverse the search space, they lack a "sense of direction." Informed search introduces intelligence by utilizing expert knowledge or mathematical rules of thumb—known as heuristics—to rapidly identify and explore the most promising paths.


1. Evaluation and Heuristic Functions

Informed search is driven by "best-first search" strategies. These strategies order the nodes in the frontier (the priority queue) based on an Evaluation Function, denoted as f(p).


Greedy search relies entirely on the heuristic to make its decisions.


3. A* Search Algorithm

A* (A-Star) is widely used because it fixes the flaws of Greedy search by keeping track of the actual costs incurred, avoiding paths that have already become too expensive.

Properties of A*

A* is considered optimally efficient, but it requires massive memory resources.

Example With Code "8 - Puzzle Problem"

def a_star_search(start_state, goal_state, heuristic='h1'):

    fringe = []

    counter = 0 # Tie-breaker for Priority Queue

    if heuristic == 'h1':
        h_start = calculate_h1(start_state, goal_state)
    else:
        h_start = calculate_h2(start_state, goal_state)

    heapq.heappush(fringe, (h_start, counter, start_state, []))

    # Track visited states and the minimum g_score (depth) it took to reach them
    explored = {start_state: 0}
    nodes_expanded = 0

    while fringe:
        f_score, _, current_state, path = heapq.heappop(fringe)

        if current_state == goal_state:
            return path, nodes_expanded

        nodes_expanded += 1
        current_g_score = len(path)

        for next_state, action in get_successors(current_state):
            new_g_score = current_g_score + 1

            # If we found a shorter path to a previously visited state, or it's a                new state
            if next_state not in explored or new_g_score < explored[next_state]:
                explored[next_state] = new_g_score

                if heuristic == 'h1':
                    h_score = calculate_h1(next_state, goal_state)
                else:
                    h_score = calculate_h2(next_state, goal_state)
                f_score = new_g_score + h_score

                counter += 1
                heapq.heappush(fringe, (f_score, counter, next_state, path + [action]))

    return None, nodes_expanded

4. Requirements for A* Optimality

A* is only mathematically guaranteed to be optimal if its heuristic (h(n)) follows strict rules:

  1. Admissibility (For Tree Search): The heuristic must be optimistic. This means h(n)h(n), where h is the true, exact cost to reach the goal. An admissible heuristic must never overestimate the cost. For example, straight-line distance is always mathematically shorter than or equal to actual winding road distances, making it perfectly admissible.

  2. Consistency / Monotonicity (For Graph Search): A stronger condition is required if the algorithm tracks visited states (Graph Search). A heuristic is consistent if h(n)c(n,a,n)+h(n). This means the estimated cost from node n to the goal can never be greater than the actual step cost to its neighbor (n) plus the neighbor's estimated cost to the goal.