A heuristic function estimates the remaining cost/distance/effort needed to reach a final goal from any node. It allows software to make fast decisions by making an educated guess on which paths are likely to be more efficient.

is also known as forward cost.

A heuristic function is specific for any given problem and will require you needing to design it. For example, a heuristic function for a word ladder problem where, given a start word, find the smallest amount of changes to reach an end word using words from a given dictionary, would look like:

def heuristic(word, end_word):
    count = 0
    for i in range(len(word)):
        if word[i] != end_word[i]:
            count += 1
    return count

From any node (i.e. any given word we are examining), we can calculate the minimum number of letter changes needed to reach the end word. This would be equal to the number of letters that are different between our current word and end word.


Manhattan Distances

Manhattan distance is a metric that measures the distance between two points by summing the absolute differences between their coordinates.

centre|no-border

Distance is calculated by moving only horizontally and vertically.


Admissible vs Consistent Heuristics

Admissible: where is actual cost from to goal -> never overestimates for all .

Consistent (monotonic): , where is a successor to by action -> estimated cost between nodes never decreases by more than actual step cost between them.

centre

Every consistent heuristic is admissible, but not vice versa


Deriving Heuristics

There are two general strategies for determining a heuristic:

  1. Relaxed problem — remove constraints
  2. Sub-problems — solve part of the problem and cache costs

centre
A relaxed problem is a simplification of your initial problem with lowered constraints. There can be multiple relaxed problems which can all give different heuristics.

For example, using the 8-puzzle above, we could allow tiles to be picked up and placed anywhere on the grid ( number of misplaced tiles) -> .

Alternatively, we could allow tiles to be overlayed on top of each other when being moved ( = the sum of Manhattan distances of tiles from goals) -> .

These simplified problems will give us an underestimate (therefore admissible) heuristic. The closer a heuristic estimate is to the true cost, the more effective the A* search.

Sub-Problems

Sub-problems involve breaking down a larger problem, finding the exact cost of solving a smaller sub-problem, and then caching the cost.

For example, using the 8-puzzle above, the sub-problem could be trying to only move 1, 2, 3, 4 to the goal positions.


Assessing Heuristics

Quality of a heuristic can be expressed as its effective branching factor , which can be determined empirically. The closer is to , the better the heuristic.

Assuming A* visits nodes and finds solution at depth :

We can approximate the effective branching factor for larger values of :

Dominant Heuristics

A heuristic dominates a heuristic if, for every state :

  • Provided both heuristic are admissible.

is a strictly better heuristic than .

If you have two admissible heuristic, but neither dominates the other, you can use both instead (at the cost of more computational power used):


await dv.view("Scripts/topic-backlinks");