用回溯算法求解数独问题

作者:Christina

翻译:疯狂的技术宅

原文:https://dev.to/christinamcmah...

未经允许严禁转载

前几天我们在《浅析常见的算法范式》中讨论了一些常见的算法范式,但是还留下了回溯算法没有解决。本文来研究回溯算法。

回溯是通过逐步构建解决方案来解决递归问题的算法。通常回溯从可能的解决方案开始,如果它不起作用,则需要回溯并尝试另一种解决方案,直到找到可行的解决方案为止。回溯在解决 CSP(约束满足问题)时特别有用,例如填字游戏、迷宫和数独等。

通常回溯算法可用于以下三种类型的问题:

  1. 需要找到可行解决方案的决策问题
  2. 需要找到最佳解决方案的优化问题
  3. 需要找到一组可行解决方案的列举问题

在本文中,我将通过解决数独问题来演示回溯策略。

解决数独问题

针对此类问题的回溯算法会尝试在每个空格中列举所有的数字,直到问题被解决为止。先从 main 方法开始:

function sudokuSolver(matrix) {
    if (solveSudoku(matrix) === true) {
        return matrix;
    }
    return '无解';
}

接下来看一看算法的主要逻辑:

const UNASSIGNED = 0;

function solveSudoku(matrix) {
    let row = 0;
    let col = 0;
    let checkBlankSpaces = false;

    // 验证数独是否已解决,如果尚未解决,则获取下一个空格的位置
    for (row = 0; row < matrix.length; row++) {
        for (col = 0; col < matrix[row].length; col++) {
            if (matrix[row][col] === UNASSIGNED) {
                checkBlankSpaces = true;
                break;
            }
        }
        if (checkBlankSpaces === true) {
            break;
        }
    }
    //当没有空格时则意味着已经解决
    if (checkBlankSpaces === false) {
        return true;
    }

    // 尝试用正确的数字填充空格
    for (let num = 1; num <= 9; num++) {
        // isSafe 用于检查在行、列或 3x3 的格子中是否已经存在了数字 num(代码实现在后面)
        if (isSafe(matrix, row, col, num)) {
            matrix[row][col] = num;

            if (solveSudoku(matrix)) {
                return true;
            }
            // 如果 num 所在的位置不合适,需要再次标记为“空格”,然后用不同的 num 回溯
            matrix[row][col] = UNASSIGNED;
        }
    }
    return false;
}

接下来看辅助函数的实现:

function isSafe(matrix, row, col, num) {
    return (
        !usedInRow(matrix, row, num) && 
        !usedInCol(matrix, col, num) && 
        !usedInBox(matrix, row - (row % 3), col - (col % 3), num)
    );
}

function usedInRow(matrix, row, num) {
    for (let col = 0; col < matrix.length; col++) {
        if (matrix[row][col] === num) {
            return true;
        }
    }
    return false;
}

function usedInCol(matrix, col, num) {
    for (let row = 0; row < matrix.length; row++) {
        if (matrix[row][col] === num) {
            return true;
        }
    }
    return false;
}

function usedInBox(matrix, boxStartRow, boxStartCol, num) {
    for (let row = 0; row < 3; row++) {
        for (let col = 0; col < 3; col++) {
            if (matrix[row + boxStartRow][col + boxStartCol] === num) {
                return true;
            }
        }
    }
    return false;
}

最后对算法进行测试:

const sudokuGrid = [
    [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]
];

console.log(sudokuSolver(sudokuGrid));

以下是通过回溯法求解数独问题的模拟动画:

sudoku being solved by backtracking

希望本文能帮你理解回溯算法,以后我门还会再讨论更多有趣的算法。

173382ede7319973.gif


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎继续阅读本专栏其它高赞文章:



疯狂的技术宅
本专栏文章首发于公众号:前端先锋 。
44.3k 声望
39.1k 粉丝
0 条评论
推荐阅读
为什么浮点数运算会产生误差
以下图的 Python 为例, 0.1 + 0.2 并不等于 0.3,8.7 / 10 也不等于 0.87,而是 0.869999…,真是太奇怪了 🤔

疯狂的技术宅12阅读 8k评论 1

ESlint + Stylelint + VSCode自动格式化代码(2023)
安装插件 ESLint,然后 File -&gt; Preference-&gt; Settings(如果装了中文插件包应该是 文件 -&gt; 选项 -&gt; 设置),搜索 eslint,点击 Edit in setting.json

谭光志34阅读 20.8k评论 9

安全地在前后端之间传输数据 - 「3」真的安全吗?
在「2」注册和登录示例中,我们通过非对称加密算法实现了浏览器和 Web 服务器之间的安全传输。看起来一切都很美好,但是危险就在哪里,有些人发现了,有些人嗅到了,更多人却浑然不知。就像是给门上了把好锁,还...

边城32阅读 7.3k评论 5

封面图
涨姿势了,有意思的气泡 Loading 效果
今日,群友提问,如何实现这么一个 Loading 效果:这个确实有点意思,但是这是 CSS 能够完成的?没错,这个效果中的核心气泡效果,其实借助 CSS 中的滤镜,能够比较轻松的实现,就是所需的元素可能多点。参考我们...

chokcoco24阅读 2.3k评论 3

你可能不需要JS!CSS实现一个计时器
CSS现在可不仅仅只是改一个颜色这么简单,还可以做很多交互,比如做一个功能齐全的计时器?样式上并不复杂,主要是几个交互的地方数字时钟的变化开始、暂停操作重置操作如何仅使用 CSS 来实现这样的功能呢?一起...

XboxYan25阅读 1.7k评论 1

封面图
在前端使用 JS 进行分类汇总
最近遇到一些同学在问 JS 中进行数据统计的问题。虽然数据统计一般会在数据库中进行,但是后端遇到需要使用程序来进行统计的情况也非常多。.NET 就为了对内存数据和数据库数据进行统一地数据处理,发明了 LINQ (L...

边城17阅读 2k

封面图
过滤/筛选树节点
又是树,是我跟树杠上了吗?—— 不,是树的问题太多了!🔗 相关文章推荐:使用递归遍历并转换树形数据(以 TypeScript 为例)从列表生成树 (JavaScript/TypeScript) 过滤和筛选是一个意思,都是 filter。对于列表来...

边城18阅读 7.9k评论 3

封面图
44.3k 声望
39.1k 粉丝
宣传栏