Javascript递归回溯数独求解器

Posted

技术标签:

【中文标题】Javascript递归回溯数独求解器【英文标题】:Javascript Recursive Backtracking Sudoku Solver 【发布时间】:2020-02-18 20:13:53 【问题描述】:

我最近在 Golang 的帮助下编写了同样的代码。

如果你熟悉 go,你可以在这里看到工作代码。

Go Playground

这是我想要在 python 中完成的任务。 Computerphile Video

我现在正在尝试将其移植到 javascript

我初始化了游戏状态。

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

这是取自数独***页面的网格。

Sudoku Wiki

我编写了以下辅助函数来获取网格上任何位置的行、列和块单元。我对它们都进行了测试,它们似乎都可以正常工作。

function isUnitUnique(unit) 
    for (let value = 1; value <= 9; value++) 
        let tally = 0;
        for (let index = 0; index < 9; index++) 
            if (unit[index] == value) 
                tally++;
            
        
        if (tally > 1) 
            return false;
        
    
    return true;

function getColumnUnit(board, column) 
    let unit = [];
    for (let row = 0; row < 9; row++) 
        unit.push(board[row][column]);
    
    return unit;

function getBlockUnit(board, x, y) 
    let unit = []
    if (x >= 0 && x <= 2)  j = 1; 
    else if (x >= 3 && x <= 5)  j = 4; 
    else if (x >= 6 && x <= 8)  j = 7; 
    if (y >= 0 && y <= 2)  i = 1; 
    else if (y >= 3 && y <= 5)  i = 4; 
    else if (y >= 6 && y <= 8)  i = 7; 
    unit.push(board[i - 1][j - 1]);
    unit.push(board[i - 1][j]);
    unit.push(board[i - 1][j + 1]);
    unit.push(board[i][j - 1]);
    unit.push(board[i][j]);
    unit.push(board[i][j + 1]);
    unit.push(board[i + 1][j - 1]);
    unit.push(board[i + 1][j]);
    unit.push(board[i + 1][j + 1]);
    return unit;

然后我使用带有以下代码的辅助函数来尝试解决这个难题。这是一种递归回溯算法。

function solve() 
    for (let row = 0; row < 9; row++) 
        for (let column = 0; column < 9; column++) 
            if (gameState[row][column] == 0) 
                for (let value = 1; value <= 9; value++) 
                    if (possible(row, column, value)) 
                        gameState[row][column] = value;
                        solve();
                        gameState[row][column] = 0;
                    
                
                return;
            
        
    
    console.log(gameState);
    return;

function possible(y, x, n) 
    let boardCopy = JSON.parse(JSON.stringify(gameState));
    boardCopy[y][x] = n;
    return isUnitUnique(boardCopy[y]) && isUnitUnique(getColumnUnit(boardCopy, x)) && isUnitUnique(getBlockUnit(boardCopy, x, y))

此控制台记录初始游戏状态不变。通过进行一些调试,我可以看到该算法正在通过网格,但是它似乎没有保留我对网格所做的更改。

提前致谢。

这里建议的是我的代码的可运行版本。当我在 sn-p 中运行它时它可以工作。使用 chrome 时不会。

var gameState = [
    [5, 3, 0, 0, 7, 0, 0, 0, 0],
    [6, 0, 0, 1, 9, 5, 0, 0, 0],
    [0, 9, 8, 0, 0, 0, 0, 6, 0],
    [8, 0, 0, 0, 6, 0, 0, 0, 3],
    [4, 0, 0, 8, 0, 3, 0, 0, 1],
    [7, 0, 0, 0, 2, 0, 0, 0, 6],
    [0, 6, 0, 0, 0, 0, 2, 8, 0],
    [0, 0, 0, 4, 1, 9, 0, 0, 5],
    [0, 0, 0, 0, 8, 0, 0, 7, 9]
]
function solve() 
    for (let row = 0; row < 9; row++) 
        for (let column = 0; column < 9; column++) 
            if (gameState[row][column] == 0) 
                for (let value = 1; value <= 9; value++) 
                    if (possible(row, column, value)) 
                        gameState[row][column] = value;
                        solve();
                        gameState[row][column] = 0;
                    
                
                return;
            
        
    
    console.log(gameState);
    return;

function possible(y, x, n) 
    let boardCopy = JSON.parse(JSON.stringify(gameState));
    boardCopy[y][x] = n;
    return isUnitUnique(boardCopy[y]) && isUnitUnique(getColumnUnit(boardCopy, x)) && isUnitUnique(getBlockUnit(boardCopy, x, y))

function isUnitUnique(unit) 
    for (let value = 1; value <= 9; value++) 
        let tally = 0;
        for (let index = 0; index < 9; index++) 
            if (unit[index] == value) 
                tally++;
            
        
        if (tally > 1) 
            return false;
        
    
    return true;

function getColumnUnit(board, column) 
    let unit = [];
    for (let row = 0; row < 9; row++) 
        unit.push(board[row][column]);
    
    return unit;

function getBlockUnit(board, x, y) 
    let unit = []
    if (x >= 0 && x <= 2)  j = 1; 
    else if (x >= 3 && x <= 5)  j = 4; 
    else if (x >= 6 && x <= 8)  j = 7; 
    if (y >= 0 && y <= 2)  i = 1; 
    else if (y >= 3 && y <= 5)  i = 4; 
    else if (y >= 6 && y <= 8)  i = 7; 
    unit.push(board[i - 1][j - 1]);
    unit.push(board[i - 1][j]);
    unit.push(board[i - 1][j + 1]);
    unit.push(board[i][j - 1]);
    unit.push(board[i][j]);
    unit.push(board[i][j + 1]);
    unit.push(board[i + 1][j - 1]);
    unit.push(board[i + 1][j]);
    unit.push(board[i + 1][j + 1]);
    return unit;

solve()

【问题讨论】:

这条线gameState[row][column] = 0;应该做什么? 我明白了,这是回溯的一部分。但是,当它到达答案时会发生什么,它不会在返回的过程中将所有内容归零吗? @bcr666 当它直接在上面调用solve() 时,它进入函数,游戏状态更新为新的潜在变量。它会一直这样做,直到达到没有有效值 (1-9) 的点并且它到达第一个 return 语句。然后从先前重新进入函数并将值更改为零,因为之前的值无法找到解决方案。最终,它到达网格上不再有 0 值的情况,它退出 for 循环并输出网格。我将添加一个 youtube 视频,显示它是在 python 中完成的。 Go 链接、Python 的 YouTube 视频、***链接...缺少的是 minimal reproducible example,它显示了正在运行的代码,包括它不工作的部分。您可能可以使用Stack Snippets 创建在 Stack Overflow 上运行她的东西,这样人们就不需要去其他网站找出您的问题。谢谢。 @HereticMonkey 感谢您的建议。我添加了代码的堆栈 sn-p。有趣的是,它运行并产生了预期的输出。我想知道为什么它不在我的 VS Code / chrome 环境中本地显示。 【参考方案1】:

感谢@HereticMonkey 建议我使用stack sn-ps 来分享我的代码。这样做时,我意识到它在这种环境中工作,并且它与在导致问题的浏览器(Chrome)中运行代码有关。还要感谢@bcr666,他还提到该算法可能会在返回的路上自动归零。

问题中列出的代码都是正确的。我添加了一项检查以防止函数在达到退出条件后继续执行。

let solved = false
function solve() 
    for (let row = 0; row < 9; row++) 
        for (let column = 0; column < 9; column++) 
            if (gameState[row][column] == 0) 
                for (let value = 1; value <= 9; value++) 
                    if (possible(row, column, value)) 
                        gameState[row][column] = value;
                        solve();
                        if (!solved) 
                            gameState[row][column] = 0;
                        
                    
                
                return;
            
        
    
    solved = true;
    console.log(gameState);
    return;

在回溯修复此问题之前检查游戏是否已解决。在它记录结果之前,它试图找到更多的解决方案。

【讨论】:

以上是关于Javascript递归回溯数独求解器的主要内容,如果未能解决你的问题,请参考以下文章

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

优化回溯算法求解数独

Gui 可视化递归回溯数独

回溯数独求解器不起作用

数独求解器回溯算法不起作用

为啥这个“数独求解器”算法不起作用