2

I've been trying to implement a backtracking algorithm to solve Sudoku in java console application. I've never implemented the algorithm before, and probably looking at few youtube videos was not enough, because it does not seem to work as I think it should.

I've filled manually the board with an actual sudoku I found online. However, it does not go past the first square. Initially I've tried to nest the whole thing in a double for-loop, but that does not seem to work either.

I've tested properly the valid-move methods so the problem is clearly not there. Appreciate any help.

public class Sudoku {

    public static int[][] createBoard(int n)
    {
        int[][] board = new int[n][n];
        for (int i=0; i<board.length; i++)
            for (int j=0; j<board[i].length; j++)
                board[i][j]=0;
        return board;
    }

    public static void printBoard(int[][] b)
    {
        int buffer=(int)Math.sqrt(b.length);
        String btm=new String(new char[buffer*buffer*3+buffer+1]).replace("\0", "_"); // fitting for all board size
        for (int i=0; i<b.length; i++)
        {
            if (i%buffer==0)
                System.out.println(btm);
            for (int j=0; j<b[i].length; j++)
            {
                if (j%buffer==0)
                    System.out.print("|");
                if (b[i][j]==0)
                    System.out.print(" _ ");
                else
                    System.out.print(" " + b[i][j] + " ");
            }
                System.out.println("|");
        }
        System.out.println(btm);
    }

    // returns true if a number can be inserted in a row. 
    public static boolean checkLegalRow(int[][] b, int row, int num)
    {
        for (int i=0; i<b.length; i++)
        {
            if (b[row][i]==num)
                return false;
        }
        return true;
    }
    // returns true if a number can be inserted in a column.
    public static boolean checkLegalCol(int[][] b, int col, int num)
    {
        for (int i=0; i<b.length; i++)
        {
            if (b[i][col]==num)
                return false;
        }
        return true;
    }

    //returns true if number can be inserted in its NxN box
    public static boolean checkLegalBox(int[][] b, int row, int col, int num)
    {
        int buffer=(int)Math.sqrt(b.length);
        for (int i=0, adjRow=row-(row%buffer); i<buffer; i++, adjRow++)
        {
            for (int j=0, adjCol=col-(col%buffer); j<buffer; j++, adjCol++)
            {
                if (b[adjRow][adjCol]==num)
                    return false;
            }
        }
        return true;
    }

    public static boolean legalMove(int[][] b, int row, int col, int num)
    {
        return checkLegalRow(b,row,num) && checkLegalCol(b,col,num) && checkLegalBox(b,row,col,num) && b[row][col]==0;
    }

    public static void solveBacktrack(int[][] b, int row, int col)
    {
        for (int k=1; k<=b.length; k++)
            {
                if (legalMove(b,row,col,k))
                {
                    b[row][col]=k;
                    if (row==b.length-1 && col==b.length-1)
                        printBoard(b);
                    else
                    {
                        //printBoard(b);
                        if (col==b.length-1)
                            solveBacktrack(b,row+1,0);
                        else
                            solveBacktrack(b,row,col+1);
                    }
                }
            }
    }

    public static void main(String[] args)
    {
        int[][] board=createBoard(9);
        board[0][1]=4; board[1][0]=6; board[2][1]=8; board[2][2]=9; board[0][3]=6; board[2][5]=3; board[1][7]=3;
        board[1][8]=1; board[3][3]=4;
        board[3][0]=2;  board[3][2]=1; board[3][4]=5; board[3][7]=7; board[3][8]=8; board[4][1]=5;
        board[4][3]=3; board[4][5]=7; board[5][0]=3; board[5][1]=6; board[5][4]=2; board[5][5]=8; board[5][8]=5;
        board[6][3]=1; board[6][6]=6; board[6][7]=4; board[7][0]=4; board[7][1]=3; board[7][8]=9; board[8][2]=6; 
        board[8][5]=9;
        printBoard(board);
        solveBacktrack(board,0,0);
    }
}
SpaceCore186
  • 586
  • 1
  • 8
  • 22
DR29
  • 121
  • 2
  • 12
  • Tell me your logic. I think you are checking validity of a number for a box just by iterating and checking whether that number is already present or not, which is wrong. Think it this way. Initially there is no number 9 in first row, first column and first block. So your logic says this block should have number 9. But there may be a case when even first row, second column and first block does not have a 9. – Shubham Jain Jun 26 '16 at 13:17
  • This is a complex algorithm, it need a lot of calculation – Youcef LAIDANI Jun 26 '16 at 13:21
  • You need Dancing Links: https://www.ocf.berkeley.edu/~jchu/publicportal/sudoku/sudoku.paper.html – Robin Mackenzie Jun 26 '16 at 13:21
  • @Shubham Jain I am not sure I'm following you. I am checking whether this number can present, and if so, I go to the next square and check it. If at some stage there won't be possible options, it will backtrack and try another number in the original loop. At least that was my understanding on the concept, based on few youtube videos. – DR29 Jun 26 '16 at 13:26
  • In legalMove you need to test if the square is 0, otherwise you are overwriting already filled squares. – stark Jun 26 '16 at 18:56

1 Answers1

3

Your check methods are wrong: You don't check if a cell is occupied as @stark mentioned in the comments.

So here is the correction for checkLegalMove:

public static boolean checkLegalMove(int[][] b, int row, int col, int num) {
  if (b[row][col] != 0) // occupied
    return false;
  // check row
  for (int i = 0; i < b[row].length; i++) {
    if (b[row][i] == num)
      return false;
  }
  // check column
  for (int i = 0; i < b.length; i++) {
    if (b[i][col] == num)
      return false;
  }
  // check box with some integer math
  for (int i = row / 3 * 3; i < (row / 3 + 1) * 3; i++) {
    if (i == row) // row already checked
      continue;
    for (int j = col / 3 * 3; j < (col / 3 + 1) * 3; j++) {
      if (j == col) // column already checked
        continue;
      if (b[i][j] == num)
        return false;
    }
  }
  return true;
}

There are also problems in the solveBacktrack method, so many, that I think it is easier to rewrite the thing with comments (no reset, wrong nesting of the loops, the last cell might be occupied already).

First I would introduce a helper function to reset stuff, so you know when you have solved the sudoku and shouldn't reset the values:

private static boolean solved;

public static void solveBacktrack(int[][] b) {
  solved = false;
  boolean found = false;
  // find first free cell and start solving
  for (int i = 0; i < 0 && !found; i < b.length; i++) {
    for (int j = 0; j < b[i].length; j++) {
      if (b[i][j] == 0) {
        found = true;
        solveBacktrack(b, i, j);
        break;
      }
    }
  }
  if (!found) // no free cell found, sudoku already solved
    solved = true;
}

And finally the recursive function:

private static void solveBacktrack(int[][] b, int row, int col) {
  for (int k = 1; k <= b.length; k++) {
    if (checkLegalMove(b, row, col, k)) {
      b[row][col] = k;
      // find next free space
      int nextRow = row, nextCol = col;
      while(nextRow < b.length && b[nextRow][nextCol] != 0) {
        if (nextCol + 1 == b[nextRow].length) {
          nextCol = 0;
          nextRow++;
        } else {
          nextCol++;
        }
      }
      if (nextRow == b.length) {
        solved = true;
        break;
      }
      solveBacktrack(b, nextRow, nextCol);
      if (!solved)
        b[row][col] = 0; // reset if not solved
      else
        break;
    }
  }
}
maraca
  • 8,468
  • 3
  • 23
  • 45