Implementing Backtracking Algorithms

Backtracking is a powerful technique used in computer science to solve problems by finding all possible solutions through a systematic search. It is especially popular when dealing with problems that have multiple valid configurations. Backtracking algorithms can efficiently solve various problems, such as the famous N-queens problem and Sudoku puzzles. In this article, we will explore how to implement backtracking algorithms using Java.

N-Queens Problem

The N-queens problem is a classical puzzle that requires placing N queens on an NxN chessboard in such a way that no two queens threaten each other. That means no two queens share the same row, column, or diagonal. The objective is to find all possible solutions to this problem.

To solve the N-queens problem using the backtracking technique, we can follow these steps:

  1. Create an empty chessboard with dimensions NxN.
  2. Start with the first row and move column-wise.
  3. Place a queen in the current column of the first row.
  4. Move to the next row and try placing a queen in each column.
  5. If a queen can be placed without threatening any other queens, move to the next row.
  6. If no column allows the placement of a queen, backtrack to the previous row and try a different column.
  7. Repeat steps 4-6 until all rows are filled with non-threatening queens.
  8. Print the final configuration as a valid solution.

Here's a Java implementation of the N-queens problem using backtracking:

public class NQueensProblem {

    private static int N = 8; // number of queens and board size
    private static int[] queens; // queens array to store column position of each queen

    private static void solveNQueens() {
        queens = new int[N];
        placeQueens(0);
    }

    private static void placeQueens(int row) {
        if (row == N) {
            printSolution();
            return;
        }

        for (int col = 0; col < N; col++) {
            if (isSafe(row, col)) {
                queens[row] = col;
                placeQueens(row + 1);
            }
        }
    }

    private static boolean isSafe(int row, int col) {
        for (int i = 0; i < row; i++) {
            if (queens[i] == col || Math.abs(queens[i] - col) == Math.abs(i - row)) {
                return false;
            }
        }
        return true;
    }

    private static void printSolution() {
        for (int row = 0; row < N; row++) {
            for (int col = 0; col < N; col++) {
                if (queens[row] == col) {
                    System.out.print("Q ");
                } else {
                    System.out.print("- ");
                }
            }
            System.out.println();
        }
        System.out.println();
    }

    public static void main(String[] args) {
        solveNQueens();
    }
}

The above implementation solves the 8-queens problem and prints all possible valid solutions.

Sudoku Solver

Another fascinating problem that can be efficiently solved using the backtracking algorithm is the Sudoku puzzle. Sudoku is a logic-based number placement game where the objective is to fill a 9x9 grid with digits from 1 to 9 in such a way that each column, each row, and each of the nine 3x3 subgrids contains all of the digits without repetition.

To solve a Sudoku puzzle using backtracking, we can proceed as follows:

  1. Find an empty cell in the Sudoku grid.
  2. Try placing a valid number (from 1 to 9) into the empty cell.
  3. If it violates the Sudoku rules, backtrack and try a different number.
  4. If all numbers have been tried without success, backtrack to the previous cell and try a different number there.
  5. Repeat steps 1-4 until all cells are filled with valid numbers.
  6. Print the final Sudoku grid as the solution.

Below is a Java implementation of the Sudoku solver using backtracking:

public class SudokuSolver {

    private static final int SIZE = 9;
    private static final int EMPTY = 0;
    private static int[][] grid;

    private static void solveSudoku() {
        if (fillSudoku(0, 0)) {
            printSolution();
        } else {
            System.out.println("No solution exists.");
        }
    }

    private static boolean fillSudoku(int row, int col) {
        if (row == SIZE) {
            row = 0;
            if (++col == SIZE) {
                return true;
            }
        }

        if (grid[row][col] != EMPTY) {
            return fillSudoku(row + 1, col);
        }

        for (int num = 1; num <= SIZE; num++) {
            if (isValid(row, col, num)) {
                grid[row][col] = num;
                if (fillSudoku(row + 1, col)) {
                    return true;
                }
            }
        }
        grid[row][col] = EMPTY;

        return false;
    }

    private static boolean isValid(int row, int col, int num) {
        for (int i = 0; i < SIZE; i++) {
            if (grid[row][i] == num || grid[i][col] == num) {
                return false;
            }
        }
        
        int startRow = 3 * (row / 3);
        int startCol = 3 * (col / 3);
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                if (grid[startRow + i][startCol + j] == num) {
                    return false;
                }
            }
        }
        
        return true;
    }

    private static void printSolution() {
        for (int row = 0; row < SIZE; row++) {
            for (int col = 0; col < SIZE; col++) {
                System.out.print(grid[row][col] + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        grid = new int[][]{
                {5, 3, 0, 0, 7, 0, 0, 0, 0},
                {6, 0, 0, 1, 9, 5, 0, 0, 0},
                {0, 9, 8, 0, 0, 0, 0, 6, 0},
                {8, 0, 0, 0, 6, 0, 0, 0, 3},
                {4, 0, 0, 8, 0, 3, 0, 0, 1},
                {7, 0, 0, 0, 2, 0, 0, 0, 6},
                {0, 6, 0, 0, 0, 0, 2, 8, 0},
                {0, 0, 0, 4, 1, 9, 0, 0, 5},
                {0, 0, 0, 0, 8, 0, 0, 7, 9}
        };

        solveSudoku();
    }
}

The above implementation solves a sample Sudoku puzzle and outputs the solution if it exists.

Conclusion

Backtracking algorithms are a vital tool in solving complex problems that possess multiple valid configurations. The N-queens problem and Sudoku puzzles are excellent examples where backtracking techniques work efficiently. By carefully implementing the backtracking algorithm in Java, we can explore all possible solutions and solve these intriguing problems effectively.


noob to master © copyleft