Backtracking Algorithm


The Backtracking Algorithm is a general algorithmic technique used to solve optimization and combinatorial problems. It is often used to find all (or some) solutions to problems that involve exploring a large search space, such as puzzles, constraint satisfaction problems, and combinatorial search.

Backtracking is typically used when the problem requires exploring different possibilities or configurations and rejecting configurations that do not meet the problem's criteria. It's often described as a depth-first search where the algorithm "backtracks" when it reaches a state that cannot lead to a solution.

How Backtracking Works

Backtracking systematically searches for a solution by exploring all possible options and undoing (or "backtracking") when it determines that a certain path will not lead to a solution.

The algorithm can be described as follows:

  1. Choice: At each step, make a choice from a set of possibilities.
  2. Constraint: Check if the choice is valid according to the problem's constraints.
  3. Goal: If the solution satisfies the goal, return it.
  4. Backtrack: If the choice does not lead to a valid solution, undo the choice (backtrack) and explore another possibility.

Backtracking Algorithm Example

A classic example of a backtracking problem is the N-Queens Problem, where the goal is to place N queens on an N×N chessboard such that no two queens threaten each other.

N-Queens Problem:

  • You are given a chessboard of size N×N, and you need to place N queens on the board such that no two queens attack each other.
  • The solution involves placing queens row by row, and for each row, the algorithm checks if a queen can be placed in a column without being attacked by another queen.

Steps to Implement Backtracking

  1. Choose: Choose an option or assign a variable.
  2. Check Constraints: Verify if the chosen option is valid according to the constraints.
  3. Recurse: Move forward to the next step if the option is valid.
  4. Backtrack: If the option doesn't lead to a solution, undo the choice (backtrack) and try the next option.

Backtracking Example: N-Queens Problem

Python Code for N-Queens Using Backtracking

def is_safe(board, row, col, n):
    # Check the column
    for i in range(row):
        if board[i] == col or abs(board[i] - col) == row - i:
            return False
    return True

def solve_n_queens(board, row, n):
    # If all queens are placed, return True
    if row == n:
        return True
    
    for col in range(n):
        if is_safe(board, row, col, n):
            board[row] = col  # Place the queen
            if solve_n_queens(board, row + 1, n):  # Recurse to place the next queen
                return True
            board[row] = -1  # Backtrack

    return False

def print_solution(board):
    for i in range(len(board)):
        row = ['.'] * len(board)
        row[board[i]] = 'Q'
        print(' '.join(row))

def n_queens(n):
    board = [-1] * n  # Initialize the board with -1
    if solve_n_queens(board, 0, n):
        print_solution(board)
    else:
        print("No solution exists")

# Example: Solve 4-Queens problem
n_queens(4)

Explanation:

  • is_safe(): This function checks if it's safe to place a queen at a given position (row, col) by ensuring no other queens are placed in the same column or diagonals.
  • solve_n_queens(): This is the backtracking function that places queens row by row. It tries placing a queen in each column of the current row, checks if the move is safe, and recursively moves to the next row. If no valid position is found, it backtracks.
  • print_solution(): This function prints the board with queens placed as "Q".

C++ Code for N-Queens Using Backtracking

#include <iostream>
#include <vector>
using namespace std;

bool is_safe(const vector<int>& board, int row, int col) {
    for (int i = 0; i < row; i++) {
        // Check if the queen can be attacked by another queen
        if (board[i] == col || abs(board[i] - col) == row - i)
            return false;
    }
    return true;
}

bool solve_n_queens(vector<int>& board, int row, int n) {
    if (row == n)  // All queens are placed
        return true;
    
    for (int col = 0; col < n; col++) {
        if (is_safe(board, row, col)) {
            board[row] = col;
            if (solve_n_queens(board, row + 1, n))
                return true;
            board[row] = -1;  // Backtrack
        }
    }
    
    return false;
}

void print_solution(const vector<int>& board) {
    for (int i = 0; i < board.size(); i++) {
        for (int j = 0; j < board.size(); j++) {
            if (board[i] == j) cout << "Q ";
            else cout << ". ";
        }
        cout << endl;
    }
}

void n_queens(int n) {
    vector<int> board(n, -1);  // Initialize the board
    if (solve_n_queens(board, 0, n)) {
        print_solution(board);
    } else {
        cout << "No solution exists" << endl;
    }
}

int main() {
    n_queens(4);  // Solve 4-Queens problem
    return 0;
}

Explanation:

  • The algorithm works similarly to the Python implementation:
    • is_safe() checks for column and diagonal safety.
    • solve_n_queens() attempts to place a queen in each row and backtracks if a valid placement cannot be found.
    • print_solution() prints the resulting board configuration.

Time and Space Complexity of Backtracking

  • Time Complexity:
    In the worst case, backtracking has a time complexity of O(b^d), where b is the branching factor (the number of choices at each step) and d is the depth of the recursion (the number of steps taken before reaching a solution). In the case of the N-Queens problem, this would be O(N!) since there are N! possible arrangements of queens.

  • Space Complexity:
    The space complexity is generally O(d) due to the recursion stack, where d is the depth of the recursion.


Applications of Backtracking

  1. Combinatorial Problems:

    • Backtracking is ideal for solving problems where the goal is to explore all possible configurations, such as permutations, combinations, and subsets.
  2. Puzzle Solving:

    • Many puzzles, like the 8-puzzle, Sudoku, and crossword puzzles, can be solved using backtracking.
  3. Constraint Satisfaction Problems (CSP):

    • Backtracking is commonly used in solving problems where variables must satisfy certain constraints. Examples include graph coloring, n-queens, and Latin squares.
  4. Optimization Problems:

    • Problems that require finding the best solution among all possible solutions can often be solved using backtracking, such as knapsack problems, traveling salesman problems, and subset sum problems.
  5. Artificial Intelligence (AI):

    • Backtracking is used in AI to solve problems like game tree exploration, pathfinding in mazes, and decision making.