Java中的数独求解器,使用回溯和递归

2022-09-01 19:31:03

我正在用Java为9x9网格编写一个数独求解器。

我有以下方法:

  • 打印网格

  • 使用给定值初始化电路板

  • 测试冲突(如果相同的数字位于同一行或 3x3 子网格中)

  • 一种逐个放置数字的方法,这需要最多的工作。

在我详细介绍该方法之前,请记住,我必须使用递归来解决它,以及回溯(在这里观看小程序作为示例 http://www.heimetli.ch/ffh/simplifiedsudoku.html )

另外,我正在通过垂直向下移动来解决这个数独,从左上角开始,通过第一列,然后通过第二列,依此类推。

到目前为止,我有以下几点:

public boolean placeNumber(int column){

    if (column == SUDOKU_SIZE){  // we have went through all the columns, game is over

        return true;

    }

    else
    {
        int row=0;  //takes you to the top of the row each time

        while (row < SUDOKU_SIZE)    loops through the column downwards, one by one
        {

            if (puzzle[row][column]==0){  //skips any entries already in there (the given values)

                puzzle[row][column]=1;   //starts with one

                while(conflictsTest(row,column)){   //conflictsTest is the method I wrote, which checks if the given parameters are in conflict with another number

                    puzzle[row][column] += 1;  

                }


           //BACK TRACKING 

                placeNumber(column);      //recursive call

            }
            else{
              row++;                  // row already has a number given, so skip it
            }
        }

        column++;              // move on to second column
        placeNumber(column);

    }
    return false; // no solutions to this puzzle
}

我标记BACKTRACKING的地方是我认为我的代码的其余部分需要去的地方。

我想到了一些事情:

  • 如果值为 10,请将该值设置回零,返回一行,然后将该值递增 1

这种回溯“策略”并不完全有效,原因如下:

  1. 如果前一行是给定值怎么办(也就是说,我不应该增加它或触摸它,而是回到我放在那里的最后一个值)

  2. 如果以前的值为 9,该怎么办?如果我把它加1,现在我们是10,这是行不通的。

有人可以帮我吗?


答案 1

我不知道你将如何解决数独,但即使你使用蛮力方法(所以在我看来你所描述的),你也应该考虑你的数据结构不合适。

我的意思是,每个单元格不应该只是一个数字,而是一组数字(可能被放置在那里)。

因此,给定的数字将表示为单例集,而空数字可以使用{1,2,3,4,5,6,7,8,9}进行初始化。然后目标是减少非单例细胞,直到所有细胞都是单例。

(请注意,在用铅笔和纸解决数独时,人们经常在空白单元格中写小数字,以跟踪那里可能的数字,只要一个人已经解决了它。

然后,当“尝试下一个数字”时,你从集合中获取下一个数字。给定的单元格没有下一个数字,因此您无法更改它们。这样,你所描述的困难就消失了(至少一点点)。

------编辑,在得知需要蛮力之后。

你的老师显然想教你递归的奇迹。非常好!

在这种情况下,我们只需要知道哪些细胞被给出,哪些不是。

这里可以使用的一种特别简单的方法是在任何非给定单元格中放置0,因为根据定义,给定单元格是1,2,3,4,5,6,7,8,9之一。

现在让我们考虑如何使递归蛮力起作用。

我们的目标是解决具有n个空单元格的数独。如果我们有一个函数可以解决具有n-1个空单元格的数独(或发出不可解决的信号),那么这个任务将很容易:

let c be some empty cell.
let f be the function that solves a sudoku with one empty cell less.
for i in 1..9
   check if i can be placed in c without conflict
   if not continue with next i
   place i in c
   if f() == SOLVED then return SOLVED
return NOTSOLVABLE

此伪代码选取一些空单元格,然后尝试适合该位置的所有数字。由于根据定义,数独只有一个解决方案,因此只有以下情况:

  • 我们选择了正确的数字。然后 f() 将找到解的其余部分并返回 SOLVED。
  • 我们选择了一个错误的数字:f()将表示数独不能用我们单元格中的错误数字解决。
  • 我们检查了所有数字,但没有人是正确的:然后我们自己得到了一个无法解决的数独,我们向呼叫者发出信号。

毋庸置疑,该算法基于这样的假设:我们只放置与当前状态不冲突的数字。例如,当在同一行,列或框中已经有一个.99

如果我们现在想想我们神秘而未知的功能是什么样的,事实证明,它将与我们已经拥有的功能几乎相同!
我们尚未考虑的唯一情况是具有0个空单元格的数独。这意味着,如果我们发现不再有空单元格,我们知道我们刚刚解决了数独,返回刚刚解决。f()

这是编写应该解决问题的递归函数时的常见技巧。我们正在编写solve(),我们知道,这个问题是可以解决的。因此,我们已经可以使用我们刚刚编写的函数,只要我们确保每次递归,问题都会以某种方式更接近解决方案。最后,我们到达所谓的基本情况,我们可以给出解决方案而无需进一步递归。

在我们的例子中,我们知道数独是可以解决的,而且,我们知道它只有一个解决方案。通过将一个片段放在一个空单元格中,我们更接近解决方案(或没有解决方案的诊断),并将新的,较小的问题递归地给出我们刚刚编写的函数。基本情况是“具有0个空单元格的数独”,这实际上是解决方案

(如果有很多可能的解决方案,事情会变得更加复杂,但我们把它留到下一课。


答案 2

首先,优化建议:在检查要放入单元格中的数字是否已经存在于同一行,列或迷你网格中时,您不必运行循环或类似的东西。您可以通过数组索引执行即时检查。

考虑 3 个 9x9 布尔双维数组:

boolean row[9][9], col[9][9], minigrid[9][9]

我们将使用第一个数组来检查数字是否存在于同一行中,第二个数组用于检查数字是否存在于同一列中,第三个数组用于迷你网格。

假设你想在你的单元格ij中放一个数字n。您将检查 row[i][n-1] 是否为真。如果是,则第 i 行已包含 n。同样,您将检查 col[j][n-1]minigrid[gridnum][n-1] 是否为真。

这里的 gridnum 是迷你网格的索引,您要插入数字的单元格位于其中。要计算单元格 i,j 的迷你网格数,请将 i 和 j 除以 3,将前者的积分部分乘以 3,然后将其与后者的积分部分相加。

这是它的外观:

gridnum = (i/3)*3 + j/3

通过查看所有 i 和 j 的 i/3 和 j/3 值,您将了解其工作原理。另外,如果您在单元格中放入数字,请同时更新数组。例如,row[i][n-1] = true

如果有部分你不理解,发表评论,我会编辑我的答案来解释它。

其次,使用递归和回溯来解决这个问题非常容易。

boolean F( i, j) // invoke this function with i = j = 0
{
If i > 8: return true // solved

for n in 1..9
 {
 check if n exists in row, column, or mini grid by the method I described

 if it does: pass ( skip this iteration )

 if it doesn't
  {
   grid[i][j] = n
   update row[][], col[][], minigrid[][]

   if F( if j is 8 then i+1 else i, if j is 8 then 0 else j+1 ) return true // solved
  }
 }
 return false // no number could be entered in cell i,j
}

推荐