Javascript - 如何优化我的数独游戏?

Javascript - How can I optimize my sudoku?

我想创建一个数独求解器,但我注意到专家级数独需要几秒钟才能显示结果... 这是我的一段代码:

function possible(board, y, x, n) {
    for (let i = 0; i < 9; i++) {
        if (board[y][i] === n || board[i][x] === n) {
            return false;
        }
    }
    const xSquare = Math.floor(x / 3) * 3;
    const ySquare = Math.floor(y / 3) * 3;

    for (let j = 0; j < 3; j++) {
        for (let i = 0; i < 3; i++) {
            if (board[ySquare + i][xSquare + j] === n) {
                return false;
            }
        }
    }
    return true;
}

function solve(board) {
    for (let y = 0; y < 9; y++) {
        for (let x = 0; x < 9; x++) {
            if (board[y][x] === 0) {
                for (let n = 1; n <= 9; n++) {
                    if (possible(board, y, x, n)) {
                        board[y][x] = n;
                        if (solve(board)) {
                            return board;
                        }
                    }
                }
                board[y][x] = 0;
                return false;
            }
        }
    }
    return board;
}

我的函数possible()是看x轴和y轴的数是否不一样,正方形(3x3)是否和当前方框的数不一样

我的函数 solver() 函数用于查看我的数组中是否没有更多的 0 以及可能的函数 possible() me return true.

我认为我的问题来自于 possible () 函数中的 double for,它从整个数组开始,但我不知道如何让它在 case 处停止...

I think my problem comes from the double for in the possible () function

我觉得不错。双 for 循环可能真的很慢,但在这种情况下,每个循环只进行 3 次迭代,所以内部循环体只执行了 9 次。没什么大不了的。

虽然有更快的方法来实现“可能性检查”。例如,通过跟踪在每一行、每一列和每一块中使用了哪些值。如果将其存储为位掩码,则很容易计算(只需几个位数学运算)给定单元格的哪些值是可能的:根本没有循环。

您可以用来加速求解器的主要技术是添加迭代约束传播:迭代查找无需搜索即可直接填充的单元格,例如 Naked Singles and Hidden Singles。根据我的经验,传播是慢解算器和快解算器之间最大的区别,但当然高效的实现也很重要。

目前您正在递归地暴力破解所有可能的组合。我没有任何绕过暴力破解的神奇解决方案。然而,我们可以让蛮力更聪明。

当前方法:

  • 您将检查所有未填写的 space。第一次迭代大约 70-75,随后的每次迭代中减少一个。
  • 对于每个这样的字段,您尝试用每个可能的数字填充它(最多 9 个选项,通常更少)
  • 然后你在一个少填字段的板上递归调用自己
  • 直到您偶然发现第一个可能的解决方案。

您当前正在执行的操作数介于 9^(开始时填充的 81 个位置)(上限,意味着在不修剪的情况下暴力破解每个选项)到更低的数量 - 例如您填充的值越多,您失败并修剪可能性树的分支的频率就越高。 实际计数将类似于:7 * 7 * 7 * ... * 6 * 6 * ... * ... 直到完成。

所以第一个可能的改进是更智能的修剪。当且仅当:

  • 它的所有字段都已填写
  • 所有未填写的字段都有超过零个可能的选项。

目前您只勾选了待填字段,也就是说 您可以创建稍后会立即被拒绝的董事会。这会创建很多不必要的分支。一些伪代码来说明:

 function possibleCount(board, x, y) {
     /*code to compute available options - e.g. number 0-9*/
     return count
}

function boardPossible(board,x, y, n) {
   board[x][y] = n
   foreach (field in board) {
       if (possibleCount(field) == 0) {
          //if there is any field that is invalidated by inserting
          //the value, we are stuck and we can prune the branch.
          return false 
       }
   }
}

这似乎需要大量额外的计算,但我们在这里进行了权衡。我们将在每一步(计算可能的棋盘选项)上花费更多的时间来尽早修剪大部分分支。
此外,您可以将计算从最初的 7 * 7 * 7 ... * 6 * 6 个选项转移到更合理的值,方法是 首先填写选项最少的字段 .
这将导致更快失败,这反过来意味着要覆盖的选项更少。

我想到的最后一点优化是,每个字段的可用选项的计数可以通过为每一行、每一列和每一个方块提供一个可用选项列表来加快。

  • 更新字段时,只需从[行、列、方块]中删除新值即可。
  • 检查字段的可用性时,检查它是否可用于每个[行、列、方块]

这将消除上述电路板检查引入的大部分开销。 不幸的是我目前在工作,但如果我有时间,我稍后会尝试做一些基准测试。