Backtracking search is a systematic algorithm for solving Constraint Satisfaction Problems (CSPs). It explores the solution space by incrementally building candidate solutions and abandoning partial candidates ("backtracking") when they cannot possibly lead to a valid complete solution.
The algorithm works by:
- Selecting an unassigned variable from the problem
- Trying each value in the variable's domain
- Checking consistency with existing assignments
- Recursively solving the reduced problem
- Backtracking when a dead-end is reached
function BACKTRACKING-SEARCH(csp)
# Returns a solution assignment or failure
return RECURSIVE-BACKTRACKING({}, csp)
function RECURSIVE-BACKTRACKING(assignment, csp)
# Returns a solution assignment or failure
# Base case: all variables assigned
if assignment is complete then
return assignment
# Select next variable to assign
var ← SELECT-UNASSIGNED-VARIABLE(VARIABLES[csp], assignment, csp)
# Try each value in the variable's domain
for each value in ORDER-DOMAIN-VALUES(var, assignment, csp) do
# Check if this value is consistent with current assignment
if value is consistent with assignment given CONSTRAINTS[csp] then
# Add the assignment
add {var = value} to assignment
# Recursively solve the rest
result ← RECURSIVE-BACKTRACKING(assignment, csp)
# If successful, propagate the solution
if result ≠ failure then
return result
# Otherwise, undo this assignment and try next value
remove {var = value} from assignment
# No valid assignment found for this variable
return failure
-
SELECT-UNASSIGNED-VARIABLE: Chooses which variable to assign next
- Can use heuristics like MRV (Minimum Remaining Values)
- Or degree heuristic (most constraining variable)
-
ORDER-DOMAIN-VALUES: Orders the values to try for a variable
- Can use least-constraining-value heuristic
- Tries to leave maximum flexibility for future assignments
- Guaranteed to find a solution if one exists
- Systematically explores the entire search space
- Space complexity is O(d·n) where d is depth and n is number of variables
- Only stores the current path, not the entire search tree
- Detects failures early and abandons infeasible partial solutions
- Avoids exploring large portions of the search space
- Straightforward to implement and understand
- Natural recursive structure maps well to the problem
- Can be enhanced with various heuristics and optimizations
- Works with any type of constraints (unary, binary, n-ary)
-
Constraint Satisfaction Problems
- Sudoku puzzles
- N-Queens problem
- Map coloring
- Scheduling problems
- SAT solving
-
Combinatorial Optimization
- When you need to find all solutions
- When the solution space is relatively small
- When constraints can quickly eliminate branches
-
Problems with Clear Constraints
- Binary constraints between variables
- Well-defined consistency checks
- Problems where partial solutions can be validated
- Very Large Search Spaces: May be too slow without optimizations
- Continuous Domains: Better suited for other techniques
- Optimization Problems: When you need the best solution, not just any solution
- Real-time Applications: May not meet strict time constraints
Maintain arc consistency by eliminating values from future variables that would violate constraints.
Preprocess the problem to reduce domain sizes before search begins.
- MRV (Minimum Remaining Values): Choose variable with fewest legal values
- Degree Heuristic: Choose variable involved in most constraints
- Least Constraining Value: Try values that rule out fewest choices for neighbors
Propagate the implications of each assignment to detect failures earlier.
def backtracking_search(csp):
"""Entry point for backtracking search."""
return recursive_backtracking({}, csp)
def recursive_backtracking(assignment, csp):
"""Recursive backtracking with assignment."""
# Check if assignment is complete
if is_complete(assignment, csp):
return assignment
# Select unassigned variable
var = select_unassigned_variable(csp.variables, assignment, csp)
# Try each value in domain
for value in order_domain_values(var, assignment, csp):
if is_consistent(var, value, assignment, csp.constraints):
# Add assignment
assignment[var] = value
# Recursive call
result = recursive_backtracking(assignment, csp)
if result is not None:
return result
# Remove assignment (backtrack)
del assignment[var]
return None # Failure-
Time Complexity: O(b^d) in worst case
- b = branching factor (size of domains)
- d = depth (number of variables)
- Can be much better with good heuristics
-
Space Complexity: O(d)
- Only stores current assignment path
- Much more efficient than storing entire tree
Backtracking search is a fundamental algorithm for CSPs that provides a complete and systematic approach to finding solutions. While it can be slow for large problems, its simplicity and ability to be enhanced with heuristics make it a valuable tool in many problem-solving scenarios. The key to effective use is understanding when the problem structure allows for efficient pruning of the search space.