数独生成器(回溯)

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

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 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;
}

 运行截图:

新手小白 如有问题 敬请指教 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值