问题
I'm writing a sudoku backtracking solver, it's getting stuck and I don't understand why. I think my recursive calls are alright. What I'm missing?
Input is read from input.txt file with the grid initial layout in a single line:
input.txt:
004020000201950070090004852005490001006000900800051300958100020010072608000080500
Edit: I mean 'stuck' as not finishing its solving of the grid
This is a sample output:
current move count is 6
3 6 4 7 2 8 1 9 0
2 0 1 9 5 0 0 7 0
0 9 0 0 0 4 8 5 2
0 0 5 4 9 0 0 0 1
0 0 6 0 0 0 9 0 0
8 0 0 0 5 1 3 0 0
9 5 8 1 0 0 0 2 0
0 1 0 0 7 2 6 0 8
0 0 0 0 8 0 5 0 0
Program:
package sudoku;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class Main {
static boolean checkRow( int row, int num, int grid[][])
{
for( int col = 0; col < 9; col++ )
if( grid[row][col] == num )
return false ;
return true ;
}
static boolean checkCol( int col, int num, int grid[][] )
{
for( int row = 0; row < 9; row++ )
if( grid [row][col] == num )
return false ;
return true ;
}
static boolean checkBox( int row, int col, int num, int grid[][] )
{
row = (row / 3) * 3 ;
col = (col / 3) * 3 ;
for( int r = 0; r < 3; r++ ){
for( int c = 0; c < 3; c++ ){
if( grid[row+r][col+c] == num )
return false ;
}
}
return true ;
}
static void printSolvedGrid(int grid[][]){
for (int i=0; i<grid.length; i++){
for (int j=0; j<grid.length;j++){
System.out.print(grid[i][j]+" ");
} System.out.println();
}
}
static int moveCounter=0;
static boolean solve(int row, int col, int [][]grid){
if (row>=grid.length){
System.out.println("solution found");
printSolvedGrid(grid);
}
if( grid[row][col] != 0 ){
next( row, col, grid ) ;
}
else {
// Find a valid number for the empty cell
for( int num = 1; num < 10; num++ )
{
if( checkRow(row,num,grid) && checkCol(col,num,grid) && checkBox(row,col,num,grid) )
{
grid[row][col] = num ;
moveCounter++;
System.out.println("current move count is " + moveCounter);
printSolvedGrid(grid);
next( row, col, grid );
return true;
}
}
}
return false;
}
public static void next( int row, int col, int [][] grid )
{
if( col < 8 ) //pass to next col
solve( row, col + 1, grid ) ;
else //pass to next row
solve( row + 1, 0, grid ) ;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(new File("input.txt")));
char gridChar[] = br.readLine().toCharArray();
int [][] grid = new int [9][9];
int gridCharIndex=0;
for (int i=0; i<grid.length; i++){
for (int j=0; j<grid.length;j++){
grid[i][j]= Integer.parseInt(gridChar[gridCharIndex++]+"");
System.out.print(grid[i][j]+" ");
} System.out.println();
}
solve(0,0, grid);
}//end method main
}//end class Main
回答1:
A key hint on why backtracking isn't happening is that you've got a boolean function called solve
whose return value is not being used at all.
In this loop:
for( int num = 1; num < 10; num++ )
{
if( checkRow(row,num,grid) && checkCol(col,num,grid) && checkBox(row,col,num,grid) )
{
grid[row][col] = num ;
moveCounter++;
System.out.println("current move count is " + moveCounter);
printSolvedGrid(grid);
next( row, col, grid );
return true;
}
}
you find a suitable candidate for the current cell, plug it in and then call next
. next
then calls solve
, but after return, you just assume that was the correct choice and return true;
So if solve
returns false, you're paying no attention to it and not trying the other choices for that cell.
回答2:
Your solution is getting stuck because while you are using recursion (not very easy to optimise your code, you can make it tail recursive so that stack doesn't build up but you haven't. Although that's a separate issue) you are not actually checking each possible branch.
Let me give you an example scenario where your code will get stuck:
Consider this the top left corner of your sudoku puzzle:
0 0 3
4 5 6
7 8 9
0 2
Your algorithm will check square [0][0] and find that 1 is a suitable fit. It will then move on to checking square [0][1] and find that 1 is unsuitable. So too when checking 2 it will find that there is already a 2 in that column. And the rest of the numbers are in the box. So it's stuck, because it will never retrace its steps back to previous decisions.
What should it do? It should recurse, it should be able to step back and change a decisions that led to this scenario. "True" recursion (i.e. one that checks all possible options of a valid recursion tree) would be able to "undo" the last decision and try the next one, namely, finding that 2 can be in the top left corner, and then continuing on.
回答3:
Fixed:
I took away the boolean and return statements :)
BTW, it appears my original test case doesn't have solution, but these two inputs do yield one:
input1.txt
900576813630090002005000900001004730200000008076900500003000600400050091712869005
input2.txt (this one is harder)
200609050604083000000700000048000200000010000001000490000001000000970103020408005
code:
static void solve(int row, int col, int [][]grid){
if (row>=grid.length){
System.out.println("solution found");
printSolvedGrid(grid);
System.exit(0);
}
if( grid[row][col] != 0 ){
next( row, col, grid ) ;
}
else {
// Find a valid number for the empty cell
for( int num = 1; num < 10; num++ )
{
if( checkRow(row,num,grid) && checkCol(col,num,grid) && checkBox(row,col,num,grid) )
{
grid[row][col] = num ;
moveCounter++;
System.out.println("current move count is " + moveCounter);
printSolvedGrid(grid);
next( row, col, grid );
}
}
grid[row][col] = 0 ;
}
}
回答4:
Not sure exactly what you mean by "stuck", is it the code is stuck or it is just not able to solve the puzzle.
Looking briefly at it, I cannot find anything wrong with the code, but at the same time, the way you use the recursion is not really right, but it should run, even if it does not find a solution...
But there are other techniques to solve sudoku puzzles that you are not using, X-Wing may be one of the most sophisticated...
Even following what you are doing now, you'll probably need to run through those tests several times...
Edit:
I run your code, ran just fine, at the last stage, it printed :
current move count is 6
3 6 4 7 2 8 1 9 0
2 0 1 9 5 0 0 7 0
0 9 0 0 0 4 8 5 2
0 0 5 4 9 0 0 0 1
0 0 6 0 0 0 9 0 0
8 0 0 0 5 1 3 0 0
9 5 8 1 0 0 0 2 0
0 1 0 0 7 2 6 0 8
0 0 0 0 8 0 5 0 0
and then it was done... it just won't solve anything anymore.
I think instead of using
row>=grid.length
as a termination condition, you should go through the grid to find if there are any '0' left. And in the case row>=grid.length
, you should start again : solve(0,0,grid)
.
That may still not solve everything, but will definitely go a bit further.
来源:https://stackoverflow.com/questions/6432794/why-is-this-sudoku-backtracking-getting-stuck