news 2026/4/22 12:32:30

回溯算法--解数独

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
回溯算法--解数独

编写一个程序,通过填充空格来解决数独问题。

数独的解法需遵循如下规则

  1. 数字1-9在每一行只能出现一次。
  2. 数字1-9在每一列只能出现一次。
  3. 数字1-9在每一个以粗实线分隔的3x3宫内只能出现一次。(请参考示例图)

数独部分空格内已填入了数字,空白格用'.'表示。

难点

N皇后问题和解数独问题对比

一、 求解策略:要“全集”还是要“特例”?

  • N皇后 (void):我们需要知道一共有多少种摆法。这意味着即使找到了一组解,也不能停,必须回溯回去,继续探索其他分支。所以不需要返回值,用全局变量result收集。

  • 解数独 (bool):数独只需要填满棋盘即可。一旦找到一条通往叶子节点的路径(即棋盘填满),就立刻层层返回true,通过if (backtracking(...)) return true;阻断后续无意义的尝试。

二、 维度差异:一维推进 vs 二维扫描这是两道题代码结构差异的根源。

  • N皇后(一维问题):

    • 规则特性:每一行只能且必须放一个皇后。

    • 推论:一旦第row行确定了位置,该行的任务就结束了,直接递归进入row + 1

    • 代码体现:递归参数只需要row。我们在思维上把二维棋盘压扁成了 N 个的一维决策步骤。

  • 解数独(二维问题):

    • 规则特性:每一行有多个空缺,且空缺位置不固定。

    • 推论:我们不能简单地“处理完这一行去下一行”。我们需要精确定位到棋盘上的每一个坐标(i, j)

    • 代码体现通用写法:每次递归都双重循环for i, for j扫描整个棋盘去找下一个 。

三、 树的宽度与操作粒度

  • N皇后:是在决定“这一行的皇后放在哪一”。(树的宽度是 N,即列数)。

  • 解数独:是在决定“这一个格子填入哪一个数字”。(树的宽度是 9,即数字 1-9)。

代码

参数

bool backtracking(vector<vector<char>>& board) {

终止条件

本题目其实不要终止条件,如果双重循环跑完了,都没有遇到 return false,说明棋盘里没有 '.' 了(填满了)

单层递归逻辑

for (int i = 0; i < board.size(); i++) { // 遍历行 for (int j = 0; j < board[0].size(); j++) { // 遍历列 // 步骤 1: 寻找空白格('.' 表示还没填数字) if (board[i][j] == '.') { // 步骤 2: 尝试填入数字 '1' 到 '9' for (char k = '1'; k <= '9'; k++) { // 步骤 3: 检查在 (i, j) 放数字 k 是否符合数独规则 if (isValid(i, j, k, board)) { board[i][j] = k; // 【做选择】:放置数字 k // 步骤 4: 递归调用 // 如果填入 k 之后,后续的递归也能成功填满棋盘,说明找到解了 if (backtracking(board)) return true; board[i][j] = '.'; // 【撤销选择】:回溯 // 如果上面的 backtracking 返回 false,说明刚才填 k 导致后面无解 // 所以要把 k 拿走,恢复成 '.',以便下一次循环尝试 k+1 } } // 步骤 5: 关键点 // 如果 1-9 都试过了,都不合法(或者导致后续无解),说明当前这一步就死路一条 // 返回 false 给上一层递归,告诉它“你之前填的数有问题,换一个吧” return false; } } }

isValid函数

其中定位当前格子所属的 3x3 九宫格的左上角起点记一下

bool isValid(int row, int col, char val, vector<vector<char>>& board) { for (int i = 0; i < 9; i++) { // 1. 检查同一列是否有重复 if (board[i][col] == val) return false; // 2. 检查同一行是否有重复 if (board[row][i] == val) return false; // 3. 检查所在的 3x3 九宫格是否有重复 // 公式说明: // (row / 3) * 3 用于定位该点所属九宫格的“左上角”行坐标 // (col / 3) * 3 用于定位该点所属九宫格的“左上角”列坐标 // board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] 是更高级的写法, // 但你代码中原本的写法也很直观,如下: } // 定位当前格子所属的 3x3 九宫格的左上角起点 int startRow = (row / 3) * 3; int startCol = (col / 3) * 3; // 遍历这个 3x3 的小方格 for (int i = startRow; i < startRow + 3; i++) { for (int j = startCol; j < startCol + 3; j++) { if (board[i][j] == val) { return false; } } } return true; // 完美,通过所有检查 }

完整代码

class Solution { private: // 回溯核心函数:尝试填充棋盘,如果填满且有效返回 true,否则返回 false bool backtracking(vector<vector<char>>& board) { // 双重循环遍历整个棋盘(9x9) for (int i = 0; i < board.size(); i++) { // 遍历行 for (int j = 0; j < board[0].size(); j++) { // 遍历列 // 步骤 1: 寻找空白格('.' 表示还没填数字) if (board[i][j] == '.') { // 步骤 2: 尝试填入数字 '1' 到 '9' for (char k = '1'; k <= '9'; k++) { // 步骤 3: 检查在 (i, j) 放数字 k 是否符合数独规则 if (isValid(i, j, k, board)) { board[i][j] = k; // 【做选择】:放置数字 k // 步骤 4: 递归调用 // 如果填入 k 之后,后续的递归也能成功填满棋盘,说明找到解了 if (backtracking(board)) return true; board[i][j] = '.'; // 【撤销选择】:回溯 // 如果上面的 backtracking 返回 false,说明刚才填 k 导致后面无解 // 所以要把 k 拿走,恢复成 '.',以便下一次循环尝试 k+1 } } // 步骤 5: 关键点 // 如果 1-9 都试过了,都不合法(或者导致后续无解),说明当前这一步就死路一条 // 返回 false 给上一层递归,告诉它“你之前填的数有问题,换一个吧” return false; } } } // 步骤 6: 终止条件 // 如果双重循环跑完了,都没有遇到 return false,说明棋盘里没有 '.' 了(填满了) // 这就是我们想要的解,直接返回 true return true; } // 辅助函数:判断在 board[row][col] 填入字符 val 是否合法 bool isValid(int row, int col, char val, vector<vector<char>>& board) { for (int i = 0; i < 9; i++) { // 1. 检查同一列是否有重复 if (board[i][col] == val) return false; // 2. 检查同一行是否有重复 if (board[row][i] == val) return false; // 3. 检查所在的 3x3 九宫格是否有重复 // 公式说明: // (row / 3) * 3 用于定位该点所属九宫格的“左上角”行坐标 // (col / 3) * 3 用于定位该点所属九宫格的“左上角”列坐标 // board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] 是更高级的写法, // 但你代码中原本的写法也很直观,如下: } // 定位当前格子所属的 3x3 九宫格的左上角起点 int startRow = (row / 3) * 3; int startCol = (col / 3) * 3; // 遍历这个 3x3 的小方格 for (int i = startRow; i < startRow + 3; i++) { for (int j = startCol; j < startCol + 3; j++) { if (board[i][j] == val) { return false; } } } return true; // 完美,通过所有检查 } public: void solveSudoku(vector<vector<char>>& board) { backtracking(board); } };
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/6 3:17:54

OBS虚拟摄像头完全指南:3步实现专业视频效果

OBS虚拟摄像头完全指南&#xff1a;3步实现专业视频效果 【免费下载链接】obs-virtual-cam obs-studio plugin to simulate a directshow webcam 项目地址: https://gitcode.com/gh_mirrors/ob/obs-virtual-cam 想要在Zoom会议中展示精心制作的OBS场景&#xff1f;或者在…

作者头像 李华
网站建设 2026/4/21 22:23:42

经济研究LaTeX模板终极使用指南:5步搞定专业论文排版

经济研究LaTeX模板终极使用指南&#xff1a;5步搞定专业论文排版 【免费下载链接】Chinese-ERJ 《经济研究》杂志 LaTeX 论文模板 - LaTeX Template for Economic Research Journal 项目地址: https://gitcode.com/gh_mirrors/ch/Chinese-ERJ 还在为学术论文的格式要求头…

作者头像 李华
网站建设 2026/4/18 8:09:21

TuneFree音乐播放器:完全免费畅享网易云VIP资源的技术指南

TuneFree音乐播放器&#xff1a;完全免费畅享网易云VIP资源的技术指南 【免费下载链接】TuneFree 一款基于Splayer进行二次开发的音乐播放器&#xff0c;可解析并播放网易云音乐中所有的付费资源。 项目地址: https://gitcode.com/gh_mirrors/tu/TuneFree 还在为心爱的歌…

作者头像 李华
网站建设 2026/4/18 7:32:31

vivado卸载入门教程:Linux平台手把手指导

Linux下彻底卸载Vivado&#xff1a;从清理残留到系统复原的实战指南你有没有遇到过这种情况&#xff1f;刚想安装新版Vivado&#xff0c;运行vivado命令时却弹出许可证错误&#xff1b;或者明明“删了”旧版本&#xff0c;终端还能调出GUI界面——这说明你的系统里还藏着一个“…

作者头像 李华
网站建设 2026/4/18 5:49:10

BetterNCM终极指南:快速打造个性化音乐播放器定制体验

BetterNCM终极指南&#xff1a;快速打造个性化音乐播放器定制体验 【免费下载链接】BetterNCM-Installer 一键安装 Better 系软件 项目地址: https://gitcode.com/gh_mirrors/be/BetterNCM-Installer 想要让单调的网易云音乐焕然一新吗&#xff1f;&#x1f3b5; 想象一…

作者头像 李华
网站建设 2026/4/22 2:28:22

VibeThinker-1.5B保姆级指南:小白10分钟上手,不用买GPU

VibeThinker-1.5B保姆级指南&#xff1a;小白10分钟上手&#xff0c;不用买GPU 你是不是一个想转行学编程的文科生&#xff1f;面对代码一头雾水&#xff0c;写个Python脚本都能报错十几行&#xff0c;网上搜解决方案又看不懂专业术语&#xff1f;别急&#xff0c;现在有个“A…

作者头像 李华