数独(日语:数独,罗马音:sūdoku,意为“数字单一”;英语发音:/suːˈdoʊkuː/、 /-ˈdɒk-/、 /sə-/ ,最初被称为“数字填空”)是一种基于逻辑的组合式数字填充谜题。在经典数独中,目标是将数字填入一个 9×9 的网格中,使得每一列、每一行以及组成该网格的九个 3×3 子网格(也称为“宫”“区块”或“区域”)都包含从 1 到 9 的所有数字。谜题设计者会提供一个部分填充好的网格,对于一个设计合理的谜题,该网格只有唯一解。
代码讲解:
1. 头文件与命名空间
#include <array>
#include <iostream>
namespace backtracking {
namespace sudoku_solver {
// 函数定义
}
}
作用:
-
<array>
:提供对std::array
的支持,用于存储数独的9x9网格。 -
<iostream>
:用于输入输出操作。 -
命名空间
backtracking
和sudoku_solver
将代码逻辑分组,避免命名冲突,提高可读性
2. isPossible
函数
template <size_t V>
bool isPossible(const std::array<std::array<int, V>, V>& mat, int i, int j, int no, int n) {
// 检查行和列
for (int x = 0; x < n; x++) {
if (mat[x][j] == no || mat[i][x] == no) return false;
}
// 检查3x3子网格
int sx = (i / 3) * 3, sy = (j / 3) * 3;
for (int x = sx; x < sx + 3; x++) {
for (int y = sy; y < sy + 3; y++) {
if (mat[x][y] == no) return false;
}
}
return true;
}
-
作用:
验证在位置(i, j)
填入数字no
是否合法。 -
细节:
-
行和列检查:遍历当前行和列,确保
no
未出现过。 -
子网格检查:计算当前单元格所在的3x3子网格起始位置,遍历该子网格检查重复。
-
若所有检查通过,返回
true
,否则返回false
。
-
3. printMat
函数
template <size_t V>
void printMat(const std::array<std::array<int, V>, V>& mat,
const std::array<std::array<int, V>, V>& starting_mat, int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (starting_mat[i][j] != mat[i][j]) { // 高亮新填入的数字
std::cout << "\033[93m" << mat[i][j] << "\033[0m ";
} else {
std::cout << mat[i][j] << " ";
}
if ((j + 1) % 3 == 0) std::cout << '\t'; // 列分隔符
}
if ((i + 1) % 3 == 0) std::cout << std::endl; // 行分隔符
std::cout << std::endl;
}
}
-
作用:
打印数独矩阵,高亮显示求解过程中填入的新数字。 -
细节:
-
使用ANSI转义码
\033[93m
将新数字显示为黄色。 -
每3列添加制表符,每3行换行,增强可读性。
-
4. solveSudoku
函数
template <size_t V>
bool solveSudoku(std::array<std::array<int, V>, V>& mat,
const std::array<std::array<int, V>, V>& starting_mat, int i, int j) {
// 基准情况:处理完所有行
if (i == 9) {
printMat<V>(mat, starting_mat, 9);
return true;
}
// 处理完当前行,转至下一行
if (j == 9) return solveSudoku<V>(mat, starting_mat, i + 1, 0);
// 跳过已填数字
if (mat[i][j] != 0) return solveSudoku<V>(mat, starting_mat, i, j + 1);
// 尝试填入1-9
for (int no = 1; no <= 9; no++) {
if (isPossible<V>(mat, i, j, no, 9)) {
mat[i][j] = no;
if (solveSudoku<V>(mat, starting_mat, i, j + 1)) return true;
mat[i][j] = 0; // 回溯
}
}
return false;
}
-
作用:
递归回溯求解数独。 -
细节:
-
基准情况:当
i == 9
时,所有行已处理完毕,打印结果并返回true
。 -
列越界处理:当
j == 9
时,转至下一行首列。 -
跳过已填格:若当前格已有数字,直接处理下一格。
-
尝试填入数字:循环尝试1-9,若合法则递归处理下一格。
-
回溯机制:若后续递归失败,重置当前格为0,继续尝试其他数字。
-
5. main
函数
int main() {
const int V = 9;
std::array<std::array<int, V>, V> mat = { /* 初始数独数据 */ };
backtracking::sudoku_solver::printMat<V>(mat, mat, 9);
std::cout << "Solution " << std::endl;
std::array<std::array<int, V>, V> starting_mat = mat;
backtracking::sudoku_solver::solveSudoku<V>(mat, starting_mat, 0, 0);
return 0;
}
-
作用:
初始化数独问题并启动求解。 -
细节:
-
定义初始数独矩阵
mat
,包含预填数字和空白(0)。 -
打印初始状态,调用
solveSudoku
从(0, 0)
开始求解。
-
总结
-
算法核心:回溯法,通过递归尝试所有可能数字,遇到矛盾时回溯。
-
关键优化:
isPossible
函数确保每一步的合法性,减少无效搜索。 -
用户体验:
printMat
通过颜色区分原始数字和新填数字,提升可读性。 -
适用性:针对标准9x9数独设计,代码结构清晰,易于扩展(如支持其他尺寸需调整子网格计算逻辑)。
最终结果: