From 020940d7881ce11035f512043b8f12e15d168dd8 Mon Sep 17 00:00:00 2001 From: Li Yanxiao Date: Sat, 21 Oct 2023 14:55:44 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=AE=9E=E9=AA=8C=E7=9A=84?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++ JudgeMatrix.c | 66 ++++++++++++++++++++++++++++++++ Makefile | 23 ++++++++++++ PrintMatrix.c | 21 +++++++++++ README.md | 36 ++++++++++++++++++ RandomMatrix.c | 56 +++++++++++++++++++++++++++ SolveMatrix.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++++ SudokuMatrix.c | 31 +++++++++++++++ SudokuMatrix.h | 96 +++++++++++++++++++++++++++++++++++++++++++++++ main.c | 85 +++++++++++++++++++++++++++++++++++++++++ 10 files changed, 518 insertions(+) create mode 100644 .gitignore create mode 100644 JudgeMatrix.c create mode 100644 Makefile create mode 100644 PrintMatrix.c create mode 100644 RandomMatrix.c create mode 100644 SolveMatrix.c create mode 100644 SudokuMatrix.c create mode 100644 SudokuMatrix.h create mode 100644 main.c 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