Core Logic and Mathematical Principles
The essence of Heuristic Search lies in introducing external prior knowledge to quantitatively assess the unknown search space, thereby breaking the disorder of blind search. Its mathematical foundation is the Evaluation Function:
$$f(x) = g(x) + h(x)$$
- $g(x)$: the actual cost from the initial state to the current state $x$ (known, deterministic).
- $h(x)$: the estimated cost from the current state $x$ to the goal state (unknown, predicted).
The Correspondence between A Algorithm and IDA Algorithm
- *A Algorithm**: Based on the breadth-first search (BFS) framework. It uses a priority queue (heap) to maintain states, popping the node with the smallest $f(x)$ for expansion each time.
- *IDA Algorithm**: Based on the iterative deepening search (IDS) framework. It traverses using depth-first search (DFS) and truncates backtracking when the current node's $f(x) > max\text{_}dep$.
The Design Principle of the Evaluation Function $h(x)$: Admissibility
Let the true shortest distance from state $x$ to the goal be $h^*(x)$. To ensure that the algorithm can find the optimal solution, $h(x)$ must satisfy:
$$0 \le h(x) \le h^*(x)$$
That is, $h(x)$ must be optimistic, never exceeding the actual cost (never overestimating).
- If $h(x) = 0$: the algorithm degenerates to a regular Dijkstra or IDS, losing its heuristic effect, resulting in state space explosion.
- If $h(x) = h^*(x)$: the algorithm takes on the "God's Eye View," moving straight towards the goal state with zero blind branches.
- If $h(x) > h^*(x)$: overly aggressive pruning may lead to a very fast search speed, but it will miss the optimal solution, causing the algorithm's correctness to collapse.
State Design and Algorithm Derivation
Taking the classic grid graph/puzzle transformation as an example, we derive the art of designing $h(x)$ and the state transitions of IDA*.
1. Classic Evaluation Function Design Models
- Manhattan Distance: If movement is restricted to up, down, left, and right in the grid, the sum of the absolute differences of the coordinates between state $x$ and the target state $T$.
$$h(x) = |x.row - T.row| + |x.col - T.col|$$
- Hamming Distance / Number of Misplaced Tiles: The number of elements in the current state that do not match the target state positions. Commonly used in graph theory models where the state transformation step count is constant at 1.
2. IDA* State Transition and Pruning Derivation
Define the function bool dfs(int dep, int max_dep, int prev_op), with the state implicitly recorded in global or bitwise operations.
- Core Pruning Equation: At each step of DFS, calculate in real-time:
$$\text{if } dep + h(current) > max\text{_}dep \text{ then return false}$$
- Incremental Maintenance of $h(x)$ (Key Optimization): If the entire state is re-evaluated to calculate $h(x)$ at each step of transition, the complexity will worsen to $O(Size)$. An efficient implementation calculates the delta change brought by the transition (Delta maintenance):
$$h(next) = h(current) + \text{Delta } h$$
For example: moving a misplaced element back to its correct position results in $\text{Delta } h = -1$; moving towards a more misaligned position results in $\text{Delta } h = +1$.
C++ Standard Source Code
Below is the standard industrial-grade template for the IDA* algorithm solving grid path/state transformation problems, which includes incremental evaluation functions and strict boundary pruning.
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
struct Point {
int x, y;
};
// Assume target coordinates
const Point target = {3, 3};
const int dx[4] = {-1, 1, 0, 0};
const int dy[4] = {0, 0, -1, 1};
// Extremely concise and strictly admissible Manhattan distance heuristic function
inline int get_heuristic(int cx, int cy) {
return std::abs(cx - target.x) + std::abs(cy - target.y);
}
bool success = false;
int next_bound = 2e9; // Record the minimum out-of-bounds cost for the next iteration
// dep: current depth, bound: maximum $f(x)$ limit for the current round, px/py: current coordinates, pre_dir: previous direction (for deduplication)
void idastar_dfs(int dep, int bound, int cx, int cy, int pre_dir) {
int h = get_heuristic(cx, cy);
int f = dep + h;
// Core heuristic pruning: current actual cost + optimistic estimated cost exceeds threshold
if (f > bound) {
next_bound = std::min(next_bound, f); // Dynamically update the tighter upper bound for the next iteration
return;
}
if (cx == target.x && cy == target.y) {
success = true;
return;
}
for (int i = 0; i < 4; ++i) {
// Logical pruning: prevent moving back to the same spot (exclude operations that cancel each other)
if ((i ^ 1) == pre_dir && dep > 0) continue;
int nx = cx + dx[i];
int ny = cy + dy[i];
// Feasibility pruning: boundary check (assuming grid size is 4x4)
if (nx < 0 || nx >= 4 || ny < 0 || ny >= 4) continue;
idastar_dfs(dep + 1, bound, nx, ny, i);
if (success) return; // Instantly break the recursion tree, refusing unnecessary backtracking
}
}
int solve(int start_x, int start_y) {
// Set the initial bound directly to the heuristic value of the initial state
int bound = get_heuristic(start_x, start_y);
while (!success && bound <= 100) { // 100 is a safe search depth upper limit
next_bound = 2e9;
idastar_dfs(0, bound, start_x, start_y, -1);
if (success) return bound;
if (next_bound == 2e9) break; // The entire connected component has been searched, no solution
bound = next_bound; // Iteratively deepen the bound value
}
return -1;
}
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(nullptr);
int sx, sy;
if (std::cin >> sx >> sy) {
int ans = solve(sx, sy);
std::cout << ans << "\n";
}
return 0;
}
NOIP Practical Pitfall Guide
- The evaluation function does not meet admissibility ($h(x) > h^*(x)$) leading to non-optimal answers When designing complex graph theory or combinatorial optimization problems, some contestants mistakenly inflate the estimated value in pursuit of rapid pruning. For example, in seeking the shortest path, setting the evaluation function to "the square of the Manhattan distance" directly violates the $h(x) \le h^*(x)$ principle, causing IDA* to prematurely prune the truly optimal path due to over-optimistic evaluation of the first path taken, ultimately resulting in a WA (wrong answer).
- *Incorrect updates to IDA iterative bounds leading to infinite loops or TLE**
When backtracking is triggered by
f > boundpruning in DFS, the next round'sboundcannot simply be incremented withbound++. If the problem's steps are non-continuous (e.g., each transformation costs more than 1), mechanically executingbound++will lead to countless useless searches. It is essential to introduce a global variablenext_bound = min(next_bound, f), precisely aligning the next round's bound with the minimum cost among all out-of-bounds branches. Forgetting to resetnext_boundto infinity will prevent the bound from increasing, leading to a deadlock in the first layer loop.
Classic NOIP/Luogu Problems
1. Luogu P1379 Eight Puzzle Problem
- Problem Description: On a $3 \times 3$ board, there are eight pieces (1-8) and one empty space (0). Given the initial state, allow the empty space to swap with adjacent pieces, and find the minimum number of steps to reach the target state.
- Essential Nature of the Problem: Single-source shortest path in a permutation space.
- Core Problem-Solving Ideas:
- State Design: Use a struct or
stringto store the 3x3 layout. - Evaluation Function Design: Design the $h(x)$ value as the total Manhattan distance of all non-zero numbers in the current state from their final target positions. Since each swap can at most bring one number closer to the target, this $h(x)$ strictly satisfies $h(x) \le h^*(x)$, enabling IDA* to solve this problem efficiently.
2. Luogu P2324 [SCOI2005] Knight's Spirit
- Problem Description: On a $5 \times 5$ board, there are 12 white knights, 12 black knights, and one empty space. The goal is to reach the specified final layout of the board within 15 moves, using the knight's "L-shape" movement.
- Essential Nature of the Problem: High-dimensional constrained heuristic search with a move limit.
- Core Problem-Solving Ideas:
- Evaluation Function Design: Count the number of misplaced tiles in the current board compared to the target board. Since each knight's move can at most place one knight back in its correct position, the remaining required moves must be at least equal to the number of misplaced tiles, thus $h(x) = \text{number of misplaced tiles}$.
- Boundary Pruning: If $dep + h(x) > 15$ or $> max\text{_}dep$, backtrack immediately. The strong limitation of 15 moves keeps the IDA* search tree tightly constrained to a small scale.