刚开始看到软工课的这个项目要求时,我的内心几乎是崩溃的= =……
毕竟对于我一个用人脑解数独都费劲的人来说,让我用电脑编程做个生成数独并解数独的小型项目真的是很扎心。
但是说归说,还是要挑战一下自己的哈……
今天撰写整个项目的生成数独终局这一部分,由于整个项目还没有完全写完,输入输出部分还没有按照项目需求实现文件输入输出,暂且用普通的printf()、scanf()来写倒也无伤大雅,毕竟这篇博文只展示关键代码,之后整个项目写完我会更新博客。
最开始我想的是用随机生成+回溯递归的方法去生成数独的终局。但是就有一个很关键的问题我没办法解决(欢迎各位在下面留言指教):无法确保每次随机生成的终局是不重复的,而我们项目需求中最大的测试输入为生成100,0000组终局,显然依次判别比较是否是重复终局这一方案不可行。
于是我开始上网搜索并且请教大佬们有没有特殊的一些数独性质可以帮助我通过一些规律生成数独终局。
数独的规则是每行、每列、每个小九宫格内不出现重复的数字,其实只要严格遵守了这些规则然后按照一定的次序打乱即可。
观察下图:
可见只需将a在1、4、7行分别放置在1、2、3列的位置,然后2、5、8行将列数加3, 3、6、9行列数加6,然后依次排列a~i这些数字即可。
需求中要求左上角第一个固定为某个特定值,那么使b~i分别代表8个不同的数共可以有8!(40320)种组合。我们仍可以发现,将小九宫格内任意两行或两列交换仍可使整个大九宫格满足要求(当然这里除了第一行第一列不可以交换,因为左上角数值固定)。为满足题目要求达到100,0000种终局,只交换4~6、7~9行(列)即可,这样每种终局可衍生出(2*C23)*(2*C23)共36种终局,36*40320>100,0000种终局。
想明白这些基本生成终局的部分就解决了,但是对于如何保证不重复的移动交换每组数字这也是个问题。在请教某位大佬后,我get了一个神奇的操作——next_permutation(),如果我们用数组change记录每行数字相对第一行要右移的格数,那么我们每次只要得到change数组不同的排列即可控制每行的数字。而这个next_permutation()函数恰好可以得到某一序列的全排列,且是当前序列的下一个升序排列。
至此,基本上所有生成数独终局的问题就都解决了。下面附上本部分代码:
1 void create(int n)//学号末位30 左上角为4 2 { 3 int change[9] = { 0,3,6,1,4,7,2,5,8 }; 4 for (int i = 0; i < 6 && n; i++) 5 { 6 if (i)//交换456行 7 next_permutation(change + 3, change + 6); 8 for (int j = 0; j < 6 && n; j++) 9 { 10 if (j) next_permutation(change + 6, change + 9);//交换789行 11 char num[10] = "412356789"; 12 for (int k = 0; k < 40320 && n; k++) 13 { 14 if (k)next_permutation(num + 1, num + 9); 15 for (int m = 0; m < 9; m++) 16 { 17 printf("%c", num[change[m] % 9]); 18 for (int n = 1; n < 9; n++) 19 { 20 printf( " %c",num[(n + change[m]) % 9]); 21 } 22 printf("\n"); 23 } 24 if (--n) printf("\n"); 25 } 26 } 27 change[6] = 2, change[7] = 5, change[8] = 8;//还原789行 28 } 29 }
4.11更新【主要更改了输出部分,变为文件输出】
void create(int n) { FILE* output_create; output_create = fopen("sudoku.txt", "w"); if (!output_create) { printf("CANNOT open the output file!\n"); exit(1); } int change[9] = { 0,3,6,1,4,7,2,5,8 }; for (int i = 0; i < 6 && n; i++) { if (i) next_permutation(change + 3, change + 6); for (int j = 0; j < 6 && n; j++) { if (j) next_permutation(change + 6, change + 9); char num[10] = "412356789"; for (int k = 0; k < 40320 && n; k++) { if (k)next_permutation(num + 1, num + 9); for (int m = 0; m < 9; m++) { fputc(num[change[m] % 9], output_create); for (int n = 1; n < 9; n++) { fputc(' ', output_create); fputc(num[(n + change[m]) % 9], output_create); } fputc('\n', output_create); } if (--n) fputc('\n', output_create); } } change[6] = 2, change[7] = 5, change[8] = 8; } fclose(output_create); }