Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Informed & Metaheuristic Search Algorithms

Open In Colab

Overview

This notebook explores five search/optimisation algorithms across two classic AI problems.

Problems

ProblemWhy chosen
8-PuzzleFinite state space — A* shines with a heuristic, direct comparison to uninformed search
N-Queens (N = 8)Classic constraint-satisfaction / optimisation benchmark — exposes local optima perfectly

Algorithms

AlgorithmFamilyProblem usedKey idea
A*Informed search8-PuzzleBest-first with f(n)=g(n)+h(n)f(n)=g(n)+h(n)
Hill ClimbingLocal searchN-QueensGreedily move to best neighbour
Simulated AnnealingLocal searchN-QueensAccept bad moves probabilistically (cooling)
Tabu SearchLocal searchN-QueensForbid recently visited moves to escape plateaus
Genetic AlgorithmEvolutionaryN-QueensEvolve a population via selection, crossover, mutation
8-Puzzle goal                     N-Queens goal (N=8)
┌───┬───┬───┐                     Q . . . . . . .
│ 1 │ 2 │ 3 │                     . . . . Q . . .
├───┼───┼───┤                     . . . . . . . Q
│ 4 │ 5 │ 6 │                     . . . . . Q . .
├───┼───┼───┤                     . . Q . . . . .
│ 7 │ 8 │   │                     . . . . . . Q .
└───┴───┴───┘                     . Q . . . . . .
                                  . . . Q . . . .
Dependencies ready.
All imports successful ✓

Part 1 — Problem Definitions

Problem A: 8-Puzzle

Same representation as in uninformed.ipynb — a 9-tuple where 0 is the blank.
Used for: A*

Problem B: N-Queens

Place N queens on an N×N chessboard so that no two queens attack each other.

State: a tuple of length N where state[i] = column of the queen in row i.
Constraint: no two queens share the same column, row, or diagonal.
Objective: minimise the number of attacking pairs → 0 = solved.

Board (N=8):  state = (0, 4, 7, 5, 2, 6, 1, 3)
Row 0 → col 0:  Q . . . . . . .
Row 1 → col 4:  . . . . Q . . .
Row 2 → col 7:  . . . . . . . Q
...

Since rows are distinct by construction, only column and diagonal conflicts matter.

Used for: Hill Climbing, Simulated Annealing, Tabu Search, Genetic Algorithm

8-Puzzle start : (1, 2, 5, 3, 4, 0, 6, 7, 8)
Manhattan h    : 11
Is solvable    : True  (inversions=2)
Sample N-Queens state : (3, 4, 6, 7, 2, 5, 0, 1)
Conflicts             : 6
Move-neighbors count  : 56
Swap-neighbors count  : 28
<Figure size 800x350 with 2 Axes>
Visualisation helpers ready ✓

Part 2 — A* Search (8-Puzzle)

How it works

A* extends UCS by adding a heuristic h(n)h(n) that estimates the remaining cost to the goal.

f(n)=g(n)+h(n)f(n) = g(n) + h(n)
TermMeaning
g(n)g(n)Actual cost from start to nn
h(n)h(n)Estimated cost from nn to goal (heuristic)
f(n)f(n)Estimated total cost through nn
PQ: [(f=h(start), g=0, start)]
while PQ not empty:
    f, g, node = heappop(PQ)
    if goal(node): return path
    for each neighbor:
        new_g = g + step_cost
        new_f = new_g + h(neighbor)
        if cheaper than seen: heappush(PQ, (new_f, new_g, neighbor))

Heuristic: Manhattan Distance

h(n)=∑i=18(∣rowicurrent−rowigoal∣+∣colicurrent−coligoal∣)h(n) = \sum_{i=1}^{8} \left( |row_i^{current} - row_i^{goal}| + |col_i^{current} - col_i^{goal}| \right)
  • Admissible — never overestimates (each tile needs at least that many moves)

  • Consistent — satisfies the triangle inequality

  • Dramatically fewer nodes expanded than BFS/UCS

BFS/UCSA*
GuidanceNone — blindHeuristic directs search toward goal
Nodes expandedO(bd)O(b^d)Often O(bd/2)O(b^{d/2}) in practice
Optimality✅✅ (admissible h)
=== A* (Manhattan Distance heuristic) ===
  Start : (1, 2, 5, 3, 4, 0, 6, 7, 8)
  h(start) = 11 (Manhattan distance)

  Solution depth    : 21 moves
  Total cost        : 21
  Nodes expanded    : 862
  Max frontier size : 516
  Time              : 0.0284 s

  Move sequence: Down → Left → Up → Left → Down → Right → Right → Up → Left → Left → Up → Right → Right → Down → Left → Up → Left → Down → Down → Right → Right
<Figure size 2660x260 with 14 Axes>
Harder puzzle  : (4, 1, 3, 2, 6, 0, 7, 5, 8)
Manhattan h    : 7
Solvable       : True

                             BFS          A*   Speedup
-------------------------------------------------------
  Nodes expanded                96          10   A* expands 9.6× fewer nodes
  Max frontier                  74          11   
  Solution depth                 7           7   
  Time (s)                 0.00086     0.00041   

Part 3 — Hill Climbing (N-Queens)

How it works

Hill Climbing is the simplest local search strategy.
At each step, move to the best neighbour (lower conflicts).
Stop when no neighbour improves the current state — a local minimum.

state = random start
while True:
    best_neighbour = argmin(conflicts(n) for n in neighbours(state))
    if conflicts(best_neighbour) >= conflicts(state):
        break          # stuck at local optimum
    state = best_neighbour

Variants used

VariantStrategy
Steepest ascentAlways move to the globally best neighbour
Random restartRe-run from a new random start when stuck

Weakness

Hill Climbing gets trapped in local optima — states that are better than all neighbours but not optimal.
For 8-Queens, ~14% of random starts lead directly to a solution; the rest get stuck.

=== Hill Climbing (Steepest-Ascent + Random Restart) ===
  Found solution  : True
  Final conflicts : 0
  Restarts used   : 2
  Total iterations: 10
  Time            : 0.00887 s
  State           : (6, 2, 7, 1, 4, 0, 5, 3)
<Figure size 450x450 with 1 Axes>

Part 4 — Simulated Annealing (N-Queens)

How it works

Simulated Annealing (SA) escapes local optima by occasionally accepting worse moves.
The probability of accepting a worse move decreases over time as the temperature cools.

P(accept worse)=e−ΔE/TP(\text{accept worse}) = e^{-\Delta E / T}
T = T_initial
state = random start
while T > T_min:
    neighbour = random_neighbour(state)
    ΔE = conflicts(neighbour) - conflicts(state)
    if ΔE < 0:              # improvement → always accept
        state = neighbour
    elif random() < exp(-ΔE / T):   # worse → accept with probability
        state = neighbour
    T = T * cooling_rate    # lower temperature

Analogy

The algorithm mimics annealing in metallurgy — metal heated, then slowly cooled to reach a low-energy crystalline state. High temperature = lots of randomness; low temperature = greedy.

Key parameters

ParameterEffect
T_initialHigher → more initial exploration
cooling_rate (< 1)Closer to 1 → slower cooling, better solution, slower runtime
T_minWhen to stop
=== Simulated Annealing ===
  Found solution  : True
  Final conflicts : 0
  Iterations      : 594
  Time            : 0.00984 s
  State           : (2, 4, 1, 7, 5, 3, 6, 0)
<Figure size 1100x400 with 2 Axes>

Part 5 — Tabu Search (N-Queens)

How it works

Tabu Search escapes local optima by forbidding recently visited moves (tabu list).
Unlike SA, it is entirely deterministic after initialisation.

state     = random start
tabu_list = deque(maxlen=tabu_tenure)

while not stopping condition:
    candidates = [non-tabu neighbours]
    best       = argmin(conflicts(n) for n in candidates)
    
    if conflicts(best) == 0: return best
    
    state = best
    tabu_list.append(last_move)   # forbid reverse of this move

Aspiration criterion

Even if a move is on the tabu list, accept it if it leads to the best solution seen so far.
This prevents the tabu list from blocking globally optimal moves.

Key parameters

ParameterEffect
tabu_tenureHow long a move stays forbidden — too short = cycling, too long = missed moves
NeighbourhoodMove (one queen to any column) vs swap; move is richer
=== Tabu Search ===
  Found solution  : True
  Final conflicts : 0
  Iterations      : 8
  Time            : 0.00549 s
  State           : (5, 3, 6, 0, 7, 1, 4, 2)
<Figure size 1100x400 with 2 Axes>

Part 6 — Genetic Algorithm (N-Queens)

How it works

A Genetic Algorithm (GA) evolves a population of candidate solutions through generations using biological operators.

population = [random_state() for _ in range(pop_size)]

for generation in range(max_gens):
    fitness   = [fitness_fn(s) for s in population]
    parents   = selection(population, fitness)    # favour low-conflict states
    offspring = [crossover(p1, p2) for p1,p2 in pairs(parents)]
    offspring = [mutate(c) for c in offspring]
    population = offspring
    if any(fitness(s)==0): return best

Genetic operators for N-Queens

OperatorImplementation
RepresentationPermutation chromosome — state[i] = column of queen in row i
Fitnessmax_possible_conflicts - nq_conflicts(state) (higher = better)
SelectionTournament selection (pick best of k random individuals)
CrossoverOrder crossover (OX1) — preserves column permutation property
MutationRandom swap of two genes — preserves permutation
  • Searches multiple points in the space simultaneously

  • Crossover can combine partial solutions from different individuals

  • Less likely to get permanently stuck in one local optimum

=== Genetic Algorithm ===
  Found solution  : True
  Final conflicts : 0
  Generations     : 2
  Time            : 0.01717 s
  State           : (2, 4, 1, 7, 0, 6, 3, 5)
<Figure size 1100x400 with 2 Axes>

Part 7 — Side-by-Side Convergence & Comparison

Overlay all four local-search/evolutionary algorithms on the same N-Queens instance to compare convergence speed and solution quality.

<Figure size 1300x450 with 2 Axes>
Algorithm           Solved  Conflicts      Iters   Time(s)
----------------------------------------------------------
  Hill Climbing          ✓          0         10   0.00887
  Sim. Annealing         ✓          0        594   0.00984
  Tabu Search            ✓          0          8   0.00549
  Genetic Algo           ✓          0          2   0.01717
<Figure size 1400x380 with 4 Axes>

Part 8 — Interactive Comparison Widget

Tune algorithm parameters and run all four local-search methods on any N-Queens problem size.
Compare solutions, convergence curves, and metrics in real time.

Loading...

Part 9 — Scalability Analysis

Benchmark all algorithms as NN increases (4 → 20 queens).
Plots show how solve rate, final conflicts, and time scale with board size.

   N   Run     HC     SA    Tabu     GA
--------------------------------------------
   4     0      0      0       0      0
   4     1      0      0       0      0
   4     2      0      0       0      0
   6     0      0      0       1      0
   6     1      0      0       0      0
   6     2      0      0       0      0
   8     0      0      0       0      0
   8     1      0      1       0      0
   8     2      0      0       0      0
  10     0      0      1       0      0
  10     1      0      0       0      0
  10     2      0      1       0      0
  12     0      0      0       0      0
  12     1      0      0       0      0
  12     2      0      1       0      0
  14     0      0      1       0      0
  14     1      0      0       0      0
  14     2      0      1       0      0
  16     0      0      1       0      0
  16     1      0      0       0      0
  16     2      0      1       0      0
  18     0      0      0       0      0
  18     1      0      1       0      1
  18     2      0      2       0      0
  20     0      0      1       0      0
  20     1      0      1       0      0
  20     2      0      2       0      1

Benchmark complete.
<Figure size 1400x400 with 3 Axes>

Part 10 — Summary & Key Takeaways

A* vs Uninformed Search (8-Puzzle)

BFS/UCSA*
GuidanceNoneManhattan distance heuristic
Nodes expanded (typical)Tens of thousandsHundreds
Optimality✅✅ (admissible h)
MemoryO(bd)O(b^d)O(bd)O(b^d) but much smaller in practice

Local Search / Metaheuristics (N-Queens)

AlgorithmEscapes local optima?MemoryDeterministicParallelism
Hill Climbing⚠️ Only via restartO(N)O(N)✅✅ Easy
Sim. Annealing✅ ProbabilisticO(N)O(N)❌ Stochastic✅ Easy
Tabu Search✅ Tabu list + aspirationO(tabu_tenure)O(tabu\_tenure)✅⚠️ Harder
Genetic Algorithm✅ Population diversityO(pop_size⋅N)O(pop\_size \cdot N)❌ Stochastic✅ Natural

When to choose which algorithm?

  • A* — when the state space is a graph with a good admissible heuristic (pathfinding, puzzle-solving)

  • Hill Climbing — fastest, but only reliable for easy instances; use with random restarts

  • Simulated Annealing — general-purpose; tune cooling schedule for quality/speed tradeoff

  • Tabu Search — strong for combinatorial problems; good when a memory of recent moves helps

  • Genetic Algorithm — excels when solutions can be combined; great for large, multimodal landscapes