4

Background

I've implemented a sudoku-solver algorithm (backtracking) that looks like this:

//Backtracking-Algorithm
public static boolean solver(int[][] board) {
    for (int i = 0; i < 9; i++) {
        for (int j = 0; j < 9; j++) {
            if (board[i][j] == 0) {
                for (int n = 1; n < 10; n++) {
                    if (checkRow(board, i, n) && checkColumn(board, j, n) && checkBox(board, i, j, n)) {
                        board[i][j] = n;
                        if (!solver(board)) {
                            board[i][j] = 0;
                        } else {
                            return true;
                        }
                    }
                }
                return false;
            }
        }
    }
    return true;
}

This solution is working fine (it can solve sudokus).

What I try to achieve

I now want to achieve that the algorithm tells me, whether there is only one solution or more solutions.

What I've tried

I tried to achieve my goal, by changing the return type to an int and count the possible solutions (stops at 2, because if there are two solutions, I can say there are "multiple" solutions). So basically, I just want to know whether there is no, one, or many solution(s):

// Backtracking-Algorithm
public int solver(int[][] board, int count) { //Starts with count = 0
  if (count < 2) {
    for (int i = 0; i < GRID_SIZE; i++) {
      for (int j = 0; j < GRID_SIZE; j++) {
        /*
         * Only empty fields will be changed
         */
        if (board[i][j] == EMPTY) {
          /*
           * Try all numbers between 1 and 9
           */
          for (int n = 1; n <= GRID_SIZE; n++) {
            /*
             * Is number n safe?
             */
            if (checkRow(board, i, n) && checkColumn(board, j, n) && checkBox(board, i, j, n)) {
              board[i][j] = n;
              if (solver(board, count) > count) {
                count++;
              } else {
                board[i][j] = 0;
              }
            }
          }
          return count;
        }
      }
    }
    return count + 1;
  }
  return count;
}

The problem is that count always goes to "1" and then the algorithm stops.

Question

What changes in the code are necessary to make it work?

  • What's the value of EMPTY? In the else condition of ``if(solver(board, count) > count)`` you should use EMPTY as well rather than 0. If EMPTY is something other than 0, then this will throw your code off – Aziz Sonawalla May 28 '20 at 14:13
  • Thanks for asking, EMPTY is 0. Do you also have an idea how to solve the main problem? –  May 28 '20 at 14:20

3 Answers3

2

The issue with your code is that it stops after it finds the first solution - more specifically, your code will never change an assigned value to a cell unless it is wrong. This is standard backtracking that you've implemented. What you need to do is, once you find one solution, you need to force your code to use other values and see if it also returns a valid solution.

Let's say this is the last row of your sudoku (where you're missing the last value), and your count is currently 0 (i.e. no solution so far):

| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0 |

Your code will try all values from 1-9 for that last cell, and once it finds out that 9 is the correct value, it will fill it in and make a recursive call.

On the recursive call, your code will not find any empty values so it will increment count by 1 (so count is now 1) and return, specifically this line: return count + 1;Because you're not making any further recursive calls at this point, the incremented count will be passed up the recursive stack and and you'll end up with a value of 1.

What you need to do is, once you've found one solution, you need to backtrack again and force increment one of the values. Your last line in the solution you found looks like this:

| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

You cannot increment the last cell because it's already at 9, so you set it to 0 / EMPTY and go to the previous value. The previous value is 8, which can be incremented to 9, so you do that and then solve that board:

| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 0 |

Maybe this doesn't return a solution, so you go back one more (setting the second last cell to 0 and increment the previous cell:

| 1 | 2 | 3 | 4 | 5 | 6 | 8 | 0 | 0 |

Now see if this gives you a solution. And so on...

TLDR: once you find a solution, you need to feed it back to your code with tighter constraints (i.e. force increment one of the valid values and see if it still gives you another solution).

Aziz Sonawalla
  • 2,482
  • 1
  • 5
  • 6
0

Thanks to this answer by Aziz Sonawalla, I think I figured it out.

The following implementation was able to solve the uniquely solveable sudoku given here. Also the algorithm is now able to solve sudokus with more than one solution (example) and recognize that there is more than one solution. If this is the case, the program will give only one of the possible solutions.

The code looks like this:

// Backtracking-Algorithm
public int[][] board2 = new int[GRID_SIZE][GRID_SIZE];

public int solver(int[][] board, int count) { // Starts with count = 0

    for (int i = 0; i < GRID_SIZE; i++) { //GRID_SIZE = 9

      for (int j = 0; j < GRID_SIZE; j++) {

        /*
         * Only empty fields will be changed
         */

        if (board[i][j] == EMPTY) { //EMPTY = 0

          /*
           * Try all numbers between 1 and 9
           */

          for (int n = 1; n <= GRID_SIZE && count < 2; n++) {

            /*
             * Is number n safe?
             */
            if (checkRow(board, i, n) && checkColumn(board, j, n) && checkBox(board, i, j, n)) {

              board[i][j] = n;
              int cache = solver(board, count);
              if (cache > count) {
                count = cache;
                for (int k = 0; k < board.length; k++) {
                  for (int l = 0; l < board.length; l++) {
                    if (board[k][l] != EMPTY) {
                      board2[k][l] = board[k][l];
                    }

                  }
                }

                board[i][j] = EMPTY;

              } else {
                board[i][j] = EMPTY;
              }

            }
          }
          return count;
        }
      }
    }
    return count + 1;
}

The solution is saved in the array board2 now.

This implementation is working as intended (as far as I know). If you find any mistakes, please leave a comment.

0

i have the exact same problem to find number of multiple solutions but I have a different code, how would you modify the code that it finds all possible solutions and gives out the number of solutions? Thank you! :)

code:


board = [
    [7,8,0,4,0,0,1,2,0],
    [6,0,0,0,7,5,0,0,9],
    [0,0,0,6,0,1,0,7,8],
    [0,0,7,0,4,0,2,6,0],
    [0,0,1,0,5,0,9,3,0],
    [9,0,4,0,6,0,0,0,5],
    [0,7,0,3,0,0,0,1,2],
    [1,2,0,0,0,7,4,0,0],
    [0,4,9,2,0,6,0,0,7]
]


def solve(bo):
    find = find_empty(bo)
    if not find:
        return True
    else:
        row, col = find

    for num in range(1,10):
        if valid(bo, num, (row, col)):
            bo[row][col] = num          

            if solve(bo):                 
                return True

            bo[row][col] = 0              

    return False


def valid(bo, num, pos):
    # Check row
    for field in range(len(bo[0])):                     
        if bo[pos[0]][field] == num and pos[1] != field:
            return False

    # Check column
    for line in range(len(bo)):
        if bo[line][pos[1]] == num and pos[0] != line:
            return False

    # Check box
    box_x = pos[1] // 3
    box_y = pos[0] // 3

    for i in range(box_y*3, box_y*3 + 3):
        for j in range(box_x * 3, box_x*3 + 3):
            if bo[i][j] == num and (i,j) != pos:
                return False

    return True


def print_board(bo):
    for i in range(len(bo)):
        if i % 3 == 0 and i != 0:
            print("- - - - - - - - - - - - - ")

        for j in range(len(bo[0])):
            if j % 3 == 0 and j != 0:
                print(" | ", end="")

            if j == 8:
                print(bo[i][j])
            else:
                print(str(bo[i][j]) + " ", end="")


def find_empty(bo):
    for i in range(len(bo)):
        for j in range(len(bo[0])):
            if bo[i][j] == 0:
                return (i, j)  # row, col

    return None
if __name__ == "__main__":
    print_board(board)
    solve(board)
    print("___________________")
    print("")
    print_board(board)


hobbes18
  • 23
  • 3