数独的解法需 遵循如下规则:
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次
数独生成器设计思路
一、整体流程
1. 生成完整数独
首先将一个9x9的二维数组(代表数独网格)初始化为全0。
然后使用回溯算法来填充这个网格,确保每一行、每一列和每一个3x3的子网格内都包含1 - 9的数字且不重复。
2. 调整难度
通过随机移除一定数量的数字来增加数独的难度。
随机选择数独网格中的位置,将该位置上的非0数字移除。
3. 评估难度
简单地通过计算数独网格中剩余的空格数量来评估难度。空格越多,难度越高。
4. 打印数独
将生成的数独网格以整齐的格式打印出来,方便查看。
二、数独生成器核心代码分析
(一) isSafe 函数:合法性检查
bool isSafe(int grid[N][N], int row, int col, int num) {
// 检查行
for (int x = 0; x < N; x++)
if (grid[row][x] == num) return false;
// 检查列
for (int x = 0; x < N; x++)
if (grid[x][col] == num) return false;
// 检查3x3子网格
int startRow = row - row % 3;
int startCol = col - col % 3;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
if (grid[i + startRow][j + startCol] == num) return false;
return true;
}
功能:该函数用于检查在数独网格 grid 的 (row, col) 位置放置数字 num 是否合法。它通过遍历当前行、当前列和对应的3x3子网格来判断是否存在重复数字。
时间复杂度:
检查行和列时,各有一个循环,循环次数为 N (在数独中 N = 9 ),时间复杂度为O(N)。
检查3x3子网格时,有两个嵌套的循环,循环次数均为3,时间复杂度为O(1)(常数时间,因为3x3是固定大小)。
总的时间复杂度为O(N),因为O(N)+O(N)+O(1) = O(N)。
(二) solveSudoku 函数:回溯算法解决数独
bool solveSudoku(int grid[N][N]) {
int row, col;
bool isEmpty = false;
for (row = 0; row < N; row++) {
for (col = 0; col < N; col++) {
if (grid[row][col] == 0) {
isEmpty = true;
break;
}
}
if (isEmpty) break;
}
// 如果没有空格,数独已解决
if (!isEmpty) return true;
// 尝试1 - 9的数字
for (int num = 1; num <= 9; num++) {
if (isSafe(grid, row, col, num)) {
grid[row][col] = num;
if (solveSudoku(grid)) return true;
grid[row][col] = 0;
}
}
return false;
}
功能:该函数使用回溯算法来填充数独网格。它首先找到第一个空的位置(值为0),然后尝试从1到9的数字,通过 isSafe 函数检查合法性,若合法则填充并递归调用自身继续填充下一个空位置,若递归调用返回 false ,则回溯并尝试下一个数字。
时间复杂度:
在最坏的情况下,对于一个空的数独网格,每次填充一个位置都可能需要尝试9个数字,而数独网格有N*N个位置(N = 9,即81个位置)。
每次调用 isSafe 函数的时间复杂度为O(N),假设每次填充一个位置平均需要尝试 k 次( k 小于等于9)。
总的时间复杂度约为O((N*N)^k),在实际中,由于数独本身的约束条件,实际复杂度会远小于这个理论上界,但仍然是指数级的。
(三) generateSudoku 函数:生成完整数独
void generateSudoku(int grid[N][N]) {
// 初始化数独网格为0
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
grid[i][j] = 0;
// 生成一个完整的数独
solveSudoku(grid);
}
功能:该函数先将数独网格初始化为全0,然后调用 solveSudoku 函数来生成一个完整的数独。
时间复杂度:取决于 solveSudoku 函数的时间复杂度,即O((N*N)^k)。
(四) removeDigits 函数:增加难度
void removeDigits(int grid[N][N], int count) {
srand(static_cast<unsigned int>(time(0)));
while (count > 0) {
int row = rand() % N;
int col = rand() % N;
if (grid[row][col]!= 0) {
grid[row][col] = 0;
count--;
}
}
}
功能:该函数用于从已生成的完整数独中随机移除 count 个数字,以增加数独的难度。它使用 rand 函数随机选择位置,并将非0位置置为0。
时间复杂度:
在最坏的情况下,可能需要循环 N*N 次才能移除 count 个数字(例如当数独中只有 count 个非0数字时)。每次循环中生成随机数和判断条件的操作时间复杂度较低,可视为常数时间。总的时间复杂度为O(N*N)。
(五) evaluateDifficulty 函数:难度评估
int evaluateDifficulty(int grid[N][N]) {
int count = 0;
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
if (grid[i][j] == 0) count++;
return count;
}
功能:该函数通过计算数独网格中空格的数量来评估数独的难度。
时间复杂度:有两层嵌套的循环,循环次数均为 N ,时间复杂度为O(N*N)。
最后就是将我们所写的函数依次调用
整体代码如图所示(DevC++版)
#include <iostream>
#include <ctime>
#include <cstdlib>
const int N = 9;
// 检查在当前位置(row, col)放置num是否合法
bool isSafe(int grid[N][N], int row, int col, int num) {
// 检查行
for (int x = 0; x < N; x++)
if (grid[row][x] == num) return false;
// 检查列
for (int x = 0; x < N; x++)
if (grid[x][col] == num) return false;
// 检查3x3子网格
int startRow = row - row % 3;
int startCol = col - col % 3;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
if (grid[i + startRow][j + startCol] == num) return false;
return true;
}
// 使用回溯算法解决数独
bool solveSudoku(int grid[N][N]) {
int row, col;
bool isEmpty = false;
for (row = 0; row < N; row++) {
for (col = 0; col < N; col++) {
if (grid[row][col] == 0) {
isEmpty = true;
break;
}
}
if (isEmpty) break;
}
// 如果没有空格,数独已解决
if (!isEmpty) return true;
// 尝试1 - 9的数字
for (int num = 1; num <= 9; num++) {
if (isSafe(grid, row, col, num)) {
grid[row][col] = num;
if (solveSudoku(grid)) return true;
grid[row][col] = 0;
}
}
return false;
}
// 生成数独
void generateSudoku(int grid[N][N]) {
// 初始化数独网格为0
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
grid[i][j] = 0;
// 生成一个完整的数独
solveSudoku(grid);
}
// 移除一些数字来增加难度
void removeDigits(int grid[N][N], int count) {
srand(static_cast<unsigned int>(time(0)));
while (count > 0) {
int row = rand() % N;
int col = rand() % N;
if (grid[row][col]!= 0) {
grid[row][col] = 0;
count--;
}
}
}
// 简单地通过剩余空格数量评估难度
int evaluateDifficulty(int grid[N][N]) {
int count = 0;
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
if (grid[i][j] == 0) count++;
return count;
}
// 打印数独
void printSudoku(int grid[N][N]) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++)
for(i=0;i<N;i++)
{
for(j=0;j<N;j++)
{
printf("%d ",grid[i][j]);
}
printf("\n");
}
}
}
int main() {
int grid[N][N];
generateSudoku(grid);
// 生成一个10到50之间的随机数作为要移除的数字数量
srand(static_cast<unsigned int>(time(0)));
int countToRemove = rand() % 41 + 10;
removeDigits(grid, countToRemove);
int difficulty = evaluateDifficulty(grid);
printf("难度为%d\n", difficulty);
printSudoku(grid);
return 0;
}
运行截图:
新手小白 如有问题 敬请指教