diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de6502c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +*.o +.vscode +SudokuMatrix \ No newline at end of file diff --git a/JudgeMatrix.c b/JudgeMatrix.c new file mode 100644 index 0000000..f55f320 --- /dev/null +++ b/JudgeMatrix.c @@ -0,0 +1,66 @@ +#include "SudokuMatrix.h" +#include + + +bool JudgeMatrix(SudokuMatrix* matrix){ + printf("The original Sudoku matrix: \n"); + PrintMatrix(matrix); + bool result = true; + // 检查每一行 + // 原理: 用一个长度为9的数组,记录每个数字出现的次数 + // 如果某个数字出现了两次,那么这个数独矩阵就不符合要求 + // 以下检查列和九宫格的原理相同。 + for (int i=0;i<9;i++){ + int row[9] = {0}; + for (int j=0;j<9;j++){ + row[matrix->matrix[i][j]-1]++; + } + for (int j=0;j<9;j++){ + if (row[j] > 1){ + printf("False:Invalid initial Sudoku matrix!\n"); + printf("The number %d in the row %d has been used!\n", j+1, i+1); + return false; + } + } + } + + // 检查每一列 + for (int i=0;i<9;i++){ + int column[9] = {0}; + for (int j=0;j<9;j++){ + column[matrix->matrix[j][i]-1]++; + } + for (int j=0;j<9;j++){ + if (column[j] > 1){ + printf("False:Invalid initial Sudoku matrix!\n"); + printf("The number %d in the col %d has been used!\n", j+1, i+1); + return false; + } + } + } + + // 检查每一个 3x3 的矩阵 + for (int i=0;i<3;i++){ + for (int j=0;j<3;j++){ + int block[9] = {0}; + for (int k=0;k<3;k++){ + for (int l=0;l<3;l++){ + block[matrix->matrix[i*3+k][j*3+l]-1]++; + } + } + for (int k=0;k<9;k++){ + if (block[k] > 1){ + printf("False:Invalid initial Sudoku matrix!\n"); + printf("The number %d in the block %d has been used!\n", k+1, i*3+j+1); + return false; + } + } + } + } + + if (result){ + printf("True:Valid initial Sudoku matrix!\n"); + } + + return result; +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9dc3a55 --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +SudokuMatrix: PrintMatrix.o main.o SudokuMatrix.o RandomMatrix.o JudgeMatrix.o SolveMatrix.o + gcc -o SudokuMatrix main.o PrintMatrix.o SudokuMatrix.o RandomMatrix.o JudgeMatrix.o SolveMatrix.o + +main.o: main.c SudokuMatrix.o + gcc -c -o main.o main.c + +PrintMatrix.o: PrintMatrix.c SudokuMatrix.o + gcc -c -o PrintMatrix.o PrintMatrix.c + +RandomMatrix.o: RandomMatrix.c SudokuMatrix.o + gcc -c -o RandomMatrix.o RandomMatrix.c + +JudgeMatrix.o: JudgeMatrix.c SudokuMatrix.o + gcc -c -o JudgeMatrix.o JudgeMatrix.c + +SolveMatrix.o: SolveMatrix.c SudokuMatrix.o + gcc -c -o SolveMatrix.o SolveMatrix.c + +SudokuMatrix.o : SudokuMatrix.h SudokuMatrix.c + gcc -c -o SudokuMatrix.o SudokuMatrix.c + +clean: + rm -rf *.o SudokuMatrix diff --git a/PrintMatrix.c b/PrintMatrix.c new file mode 100644 index 0000000..db41edf --- /dev/null +++ b/PrintMatrix.c @@ -0,0 +1,21 @@ +#include "SudokuMatrix.h" +#include + + +void PrintMatrix(SudokuMatrix *matrix){ + for (int i=0;i<9;i++){ + if (i == 0 || i == 3 || i == 6){ + printf("|-----------------------------|\n"); + } + for (int j=0;j<9;j++){ + if (j == 0 || j == 3 || j == 6){ + printf("|"); + } + printf(" %d ",matrix->matrix[i][j]); + } + // 打印最后一列的竖线 + printf("|\n"); + } + // 打印尾巴那行 + printf("|-----------------------------|\n"); +} \ No newline at end of file diff --git a/README.md b/README.md index db071fc..773e606 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,38 @@ # SudokuMatrix +> 程序设计基础:数独矩阵实验 +> +> 作者:李彦筱 + +由于教学资料中没有实验报告的模版,暂时只提交说明性文件。 + +## 程序运行 + +### 类 Unix 系统(macOS, Linux) + +- 确保您的系统已经安装了 gcc 与 make +- 在终端中打开作业所在文件夹,输入 `make` 即可编译 +- 输入 `./SudokuMatrix` 即可运行程序 + +> 输入 `make clean` 可以清除已编译的文件,只留源代码 + +### Windows 下(Visual Studio中运行) + +暂时待补充。 + +## 程序内容 + +`main.c` 中包含了 `testRandomMatrix`, `testJudgeMatrix`, `testSolveMatrix` 三个测试函数。在主函数中分别调用每一个函数,即可查看该部分的测试效果。 +`testRandomMatrix` 测试了程序生成随机数独矩阵的能力。请注意,由于题目描述要求,生成的随机数独矩阵甚至不一定满足 `judgeMatrix` 对数独矩阵的要求 + +> `judgeMatrix` 中要求数独矩阵的列不能有重复的数字;但 `randomMatrix` 生成矩阵的要求不包含这点,导致生成的矩阵绝大部分都不满足要求 + +`testJudgeMatrix` 测试了程序判断数独矩阵是否有效的能力。数独矩阵有效,仅当它每一行、每一列、每个九宫格内没有重复的两个数字。 + +`testSolveMatrix` 测试了程序对三个预定义矩阵的求解能力。这个函数需要你传递 0~2 的一个整数作为参数,以便指定你要用哪个预定义矩阵做测试。这三个矩阵: +- 第一个:满足数独矩阵的要求,有解 +- 第二个:不满足数独矩阵的要求,因此无解 +- 第三个:满足数独矩阵的要求,但是无解 +本函数不使用随机生成的数独矩阵。这是因为随机生成的数独矩阵几乎都不满足数独矩阵的要求(大概率存在列冲突),导致十个矩阵没一个是能解的… + +四个要求的函数分别处于 `PrintMatrix.c`, `RandomMatrix.c`,`JudgeMatrix.c` 和 `SolveMatrix.c` 中。矩阵类本身的定义位于 `SudokuMatrix.h` 当中,其中函数(比如构造函数,析构函数)的实现位于 `SudokuMatrix.c` 中。想要查看这些函数的实现的话,请到对应文件中寻找。我已经为每个函数写了很详细的注释了, diff --git a/RandomMatrix.c b/RandomMatrix.c new file mode 100644 index 0000000..b3a0843 --- /dev/null +++ b/RandomMatrix.c @@ -0,0 +1,56 @@ +#include "SudokuMatrix.h" +#include + + +/** + * @brief 交换两个整数的值 + * + * @param a 一个整数的地址 + * @param b 另一个整数的地址 + */ +void Swap(int*a, int *b){ + int temp = *a; + *a = *b; + *b = temp; +} + + +/** + * @brief 将一个 int 数组的内容随机打乱 + * + * @param array 需要打乱的数组 + * @param length 数组的长度 + */ +void Shuffle(int* array, int length){ + if (length != 0){ + for (int i = length - 1; i > 0; i--) { + int j = rand() % (i + 1); + Swap(&array[i], &array[j]); + } + } +} + + + +void RandomMatrix(SudokuMatrix* matrix){ + int random_row[9] = {1,2,3,4,5,6,7,8,9}; + int random_place[9] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; + + // 重置矩阵内容为全0 + for (int i=0;i<9;i++){ + for (int j=0;j<9;j++){ + matrix->matrix[i][j] = 0; + } + } + + for (int i=0;i<3;i++){ + // 先生成这九个数的顺序,再随机生成这九个数应该在哪里。 + Shuffle(random_row,9); + for (int j=0;j<3;j++){ + Shuffle(random_place,9); + for (int k=0;k<3;k++){ + matrix->matrix[i*3+j][random_place[k]] = random_row[j*3+k]; + } + } + } +} \ No newline at end of file diff --git a/SolveMatrix.c b/SolveMatrix.c new file mode 100644 index 0000000..30cd507 --- /dev/null +++ b/SolveMatrix.c @@ -0,0 +1,100 @@ +#include "SudokuMatrix.h" +#include + + +/** + * @brief 检查在指定位置是否可以放置数字num + * + * @param sudoku 一个数独矩阵 + * @param row 行数位置 + * @param col 列数位置 + * @param num 想要放置的数字 + * @return true 数字可以在这里放置 + * @return false 数字不可以在这里放置 + */ +bool isSafe(SudokuMatrix *sudoku, int row, int col, int num) { + // 检查行 + for (int i = 0; i < 9; i++) { + if (sudoku->matrix[row][i] == num) { + return false; + } + } + + // 检查列 + for (int i = 0; i < 9; i++) { + if (sudoku->matrix[i][col] == num) { + return false; + } + } + + // 检查九宫格 + 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 (sudoku->matrix[i + startRow][j + startCol] == num) { + return false; + } + } + } + return true; +} + + +/** + * @brief 使用回溯和递归方法解决数独 + * 这是实际解决数独的函数,没有完整性检查之类的东西 + * + * @param matrix 待求解的数独。这个数独的内容会改变。 + * @return true 数独被成功求解 + * @return false 数独求解失败 + */ +bool _SolveMatrix(SudokuMatrix* matrix){ + for (int row = 0; row < 9; row++) { + for (int col = 0; col < 9; col++) { + if (matrix->matrix[row][col] == 0) { + for (int num = 1; num <= 9; num++) { + if (isSafe(matrix, row, col, num)) { + matrix->matrix[row][col] = num; + if (_SolveMatrix(matrix)) { + return true; // 已找到解决方案 + } + matrix->matrix[row][col] = 0; // 回溯 + } + } + return false; // 没有解决方案 + } + } + } + return true; // 所有格子都已填满,找到解决方案 +} + + +bool SolveMatrix(SudokuMatrix *matrix, SudokuMatrix *result){ + // 检查可求解性 + if (!JudgeMatrix(matrix)){ + printf("No solution!\n"); + return false; + } + + // 复制矩阵内容 + if (matrix != result){ + for (int i=0;i<9;i++){ + for (int j=0;j<9;j++){ + result->matrix[i][j] = matrix->matrix[i][j]; + } + } + } + + // 进行求解 + bool result_bool = _SolveMatrix(result); + if (result_bool){ + printf("The solution of Sudoku matrix:\n"); + PrintMatrix(result); + return true; + } + else{ + printf("No solution!\n"); + return false; + } +} \ No newline at end of file diff --git a/SudokuMatrix.c b/SudokuMatrix.c new file mode 100644 index 0000000..9d40009 --- /dev/null +++ b/SudokuMatrix.c @@ -0,0 +1,31 @@ +#include "SudokuMatrix.h" + +SudokuMatrix* CreateMatrix(){ + SudokuMatrix *matrix = (SudokuMatrix*)malloc(sizeof(SudokuMatrix)); + // 初始化这个数组里的数为0 + for (int i=0;i<9;i++){ + for (int j=0;j<9;j++){ + matrix->matrix[i][j] = 0; + } + } + return matrix; +} + +SudokuMatrix* CreateMatrixFromArray(int matrix[9][9]){ + SudokuMatrix *newMatrix = CreateMatrix(); + // 拷贝内部的数组为输入的数组 + for(int i=0; i<9; i++){ + for(int j=0; j<9; j++){ + newMatrix->matrix[i][j] = matrix[i][j]; + } + } + return newMatrix; +} + +void DeleteMatrix(SudokuMatrix *matrix){ + // 释放整个结构体 + if (matrix != NULL) + { + free(matrix); + } +} \ No newline at end of file diff --git a/SudokuMatrix.h b/SudokuMatrix.h new file mode 100644 index 0000000..5fe4a93 --- /dev/null +++ b/SudokuMatrix.h @@ -0,0 +1,96 @@ +#ifndef SudokuMatrix_h +#define SudokuMatrix_h + +#include +#include + +/** + * @brief 数独数组对象 + * 这就是 C with Class! + * 创建对象:请调用 CreateMatrix 函数而不是直接创建一个结构体类型的对象。 + * 因为纯C里头根本没有构造函数和析构函数用,只能手搓一个 + * 注:由于纯C的内存管理实属烂到家,你需要在用完一个该对象后,主动调用本对象的析构函数 + * (就是下面那个DeleteMatrix函数)来释放内存,否则会造成内存泄漏。 + * 数独数组对象中所有内容均在堆上分配,以方便函数间传输。 + */ +typedef struct SudokuMatrix{ + int matrix[9][9]; +} SudokuMatrix; + + +/** + * @brief Create a Matrix object,无参数构造函数 + * + * @return SudokuMatrix* 一个新的数独数组对象 + */ +SudokuMatrix* CreateMatrix(); + +/** + * @brief Create a Matrix From Array object 从一个二维数组创建一个数独数组对象。 + * 数独数组的内容会从二维数组里头拷贝。 + * @param matrix + * @return SudokuMatrix* 一个新的数独数组对象 + */ +SudokuMatrix* CreateMatrixFromArray(int matrix[9][9]); + +/** + * @brief 析构函数 + * 无论何时,当你用完一个 SudokuMatrix 对象后,都应该调用这个函数来释放内存。 + * + * @param matrix + */ +void DeleteMatrix(SudokuMatrix *matrix); + + +/** + * @brief 打印一个数独数组对象 + * 以一个“看起来像数独”的形式打印出来。 + * 具体实现请见 PrintMatrix.c 喵 + * + * @param matrix + */ +void PrintMatrix(SudokuMatrix *matrix); + + +/** + * @brief 将您输入的一个数独矩阵对象的内容随机化为0-9的数字 + * 数组内任意一个元素x范围为:1<=x<=9; + * 数组每行仅有三个互不相同的数字x(x∈[1,9]) ,其他位置为数字0 + * 数组中 1-3 行中包含了 1-9 中所有的 9 个数字,4-6 行和 7-9 行也分别是如此; + * 具体实现见 RandomMatrix.c 喵 + * 警告:生成的数独矩阵仅仅满足上述要求;不保证有解。 + * + * @param matrix 数独矩阵对象。请注意,该对象当前的内容会被舍弃。 + */ +void RandomMatrix(SudokuMatrix *matrix); + + +/** + * @brief 判断输入的数独矩阵是否符合数独的要求 + * 数字 1-9 在每一行最多只能出现一次,不完整时可能是0或1次; + * 数字 1-9 在每一列最多只能出现一次,不完整时0或1次; + * 数字 1-9 在每一个被分隔开的 3x3 的矩阵内最多只能出现一次,不完整时0或1次; + * 请注意:本方法会输出矩阵,并且输出矩阵符合/不符合要求,如果不符合要求的话,以及其原因 + * 警告:本方法仅仅会输出一个问题,即使数独矩阵中可能存在多个问题。 + * + * @param matrix 判断的数独矩阵对象 + * @return true 数独矩阵符合要求 + * @return false 数独矩阵不符合要求 + */ +bool JudgeMatrix(SudokuMatrix *matrix); + + +/** + * @brief 填满一个不完整的数独矩阵 + * 如果该矩阵不满足数独的要求,将不会进行求解 + * 补全后的矩阵保存在 result 中。请注意,result 中当前的内容会被舍弃。 + * + * @param matrix 不完整的数独矩阵 + * @param result 补全后的数独矩阵 + * @return true 补全成功 + * @return false 补全失败(可能是因为无解,或者传入的矩阵不满足数独的要求) + */ +bool SolveMatrix(SudokuMatrix *matrix, SudokuMatrix *result); + + +#endif // SudokuMatrix_h \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..3a91a1e --- /dev/null +++ b/main.c @@ -0,0 +1,85 @@ +#include "SudokuMatrix.h" +#include + + +/** + * @brief 测试随机生成数独矩阵 + */ +void testRandomMatrix(){ + SudokuMatrix* matrix = CreateMatrix(); + RandomMatrix(matrix); + PrintMatrix(matrix); + DeleteMatrix(matrix); +} + + +/** + * @brief 测试判断数独矩阵的函数 + * + */ +void testJudgeMatrix(){ + SudokuMatrix* matrix = CreateMatrix(); + RandomMatrix(matrix); + JudgeMatrix(matrix); + DeleteMatrix(matrix); +} + +/** + * @brief 测试求解数独矩阵的函数 + * + * @param matrix_used 0: board0; 1: board1; 2: board2 + */ +void testSolveMatrix(int matrix_used){ + int board0[9][9] = {{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}}; +int board1[9][9] = {{8, 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}}; +int board2[9][9] = {{5, 2, 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}}; + + SudokuMatrix* matrix; + switch (matrix_used){ + case 0: + matrix = CreateMatrixFromArray(board0); + break; + case 1: + matrix = CreateMatrixFromArray(board1); + break; + case 2: + matrix = CreateMatrixFromArray(board2); + break; + } + SudokuMatrix* result = CreateMatrix(); + SolveMatrix(matrix, result); + DeleteMatrix(matrix); + DeleteMatrix(result); +} + + +int main(){ + // 初始化随机数种子 + srand(time(NULL)); + // 自选你要调用(用来测试)的函数 + testSolveMatrix(0); +} \ No newline at end of file