新的一月开始了,好吧我坦白,其实我写这篇博客也算是滥竽充数吧,因为每个月只要您写四篇原创博文,就会获得少许C币以及点亮酷炫的”恒“勋章,所以大家还请踊跃创作啊【在这里向审核者衷心致谢,您辛苦了!】
百无聊赖之际,我从网上下载了一款数独的游戏,游戏规则很简单,一个正方形,里面共有9*9个方格,每个方格中必须有一个数字【数字只能是1到9的某一个数值】,而且还有3个限制条件:1)每一行的数字不能重复【即每一行中每个数字能且只能出现一次;2)每一列的数字也不能重复;3)每一个小九宫区域内的数字也不能重复【所谓小九宫区域,指的是大正方形被分成相等的9个小正方形,每个正方形都是3*3个方块】; 当时我感觉这种游戏挺有难度也挺好玩,所以试了试,但半途中一拍大腿:为什么不让电脑帮我解决问题呢?
说干就干,其实写程序这种事,并不比做题本身来的轻松,经过N次的debug,最终貌似修成正果,其源代码如下:
#include <stdio.h> #include <stdlib.h> #include <time.h> int main() { int num[9][9], flag[81], backarr[81][2]; int i, r, c, rend, cend, back, layer, row, col, count; clock_t start, end; randstart: for (r = 0; r < 9;r++ ) for (c = 0; c < 9; c++) num[r][c] = 0; for (i = 0; i < 81; i++) flag[i] = 0; for (r = 0; r < 81; r++) for (c = 0; c < 2; c++) backarr[r][c] = 0; i = r = c = rend = cend = back = layer = row = col = count = 0;//以上这些都是用来把所有的数据与变量清零,可以不用管它们 srand((unsigned)(time)(NULL));//下面开始随机生成局面 i = rand() % 12 + 12; //我们要生成的局面开始的固定数值最少12个,最多24个,因为本人能力有限,写的算法可能招呼不来 printf("您好,欢迎来到数独世界\n"); while (count < i) { layer = rand() % 81; if (!flag[layer]) { rend = rand() % 9 + 1; row = layer / 9; col = layer % 9; for (r = 0; r < 9; r++) if (num[r][col] == rend) goto randend; for (c = 0; c < 9; c++) if (num[row][c] == rend) goto randend; for (int kl = ((layer / 9) / 3) * 3; kl < ((layer / 9) / 3) * 3 + 3; kl++) for (int kh = ((layer % 9) / 3) * 3; kh < ((layer % 9) / 3) * 3 + 3; kh++) if (num[kl][kh] == rend) goto randend; flag[layer] = 1; num[row][col] = rend; count++; } randend:; } //到这里局面已生成 /* printf("首先请输入您遇到的局面,开始有几个固定数字?\n"); scanf("%d", &r); printf("好了,数字已确定,下面请初始化局面,其中第一个数字代表数独棋盘的位置,范围是0到80;而第二个数字是填入位置中的数值,范围是1到9:\n"); for (i = 0; i < r; i++) { scanf("%d%d", &row, &col); num[row / 9][row % 9] = col; flag[row] = 1; } */ printf("这是我们随机初始化后的局面,有%d个固定数值,如下所示\n",i); for (r = 0; r < 9; r++) { for (c = 0; c < 9; c++) { if (num[r][c] != 0) printf("%d", num[r][c]); else printf("*"); } printf("\n"); } i = r = c = rend = cend = back = layer = row = col = count = 0; //变量清零,方便后来的算法思考 printf("您可以按下回车键查看这个谜题的解答\n"); system("pause"); start = clock(); //记录下程序开始“思考”的时间 while (layer < 81) //因为要测试81次,layer代表81层的每一层 { if (layer < 0) { printf("不好意思,这是一个无解的死局!请再给我一次机会...\n"); goto randstart; } if (1 == flag[layer]) //这个分支用来测试是否计算到了固定数值,如果是,则跳过这一块 { if (back == 1) { goto no_match; } //如果正处于回溯状态,就当成无满足条件的递归式回到上一层 layer++; // 不然则当成前进式计算下一层 continue; } row = layer / 9; col = layer % 9; //把这一层用来转换定位行和列的具体坐标 for (i = backarr[layer][back] + 1; i < 10; i++) //注意那个backarr,它用来当作测试数值的下限值 { for (c = 0; c < 9; c++)//检查横列 if (c >= col) { if (flag[row * 9 + c] == 1 && num[row][c] == i) goto end;//i不符合条件,刷新i } else if (num[row][c] == i) { goto end; } for (r = 0; r < 9; r++) //检查竖列 if (r >= row) { if (flag[r * 9 + col] == 1 && num[r][col] == i) goto end; } else if (num[r][col] == i) goto end; rend = ((row / 3) * 3) + 3; cend = ((col / 3) * 3) + 3; //检查九宫格 for (r = (row / 3) * 3; r < rend; r++) for (c = (col / 3) * 3; c < cend; c++) if ((r * 9 + c) >= layer) { if (flag[r * 9 + c] == 1 && num[r][c] == i) goto end; } else if (num[r][c] == i) goto end; goto match_go; end:continue; } goto no_match; match_go: num[row][col] = i; //i暂时符合限制条件,程序继续前进式计算 backarr[layer][1] = i; back = 0; layer++; continue; no_match: back = 1; //没有符合条件的数值,将状态职位回溯 layer--; } end = clock(); //程序“思考”结束时间 printf("一共用时%d毫秒,答案如下:\n", end - start); for (r = 0; r < 9; r++) //走到这里表示程序已经找到答案,下面就是给出答案 { for (c = 0; c < 9; c++) { if (num[r][c] != 0) printf("%d", num[r][c]); else printf("*"); } printf("\n"); } system("pause"); }
在这里有必要多说两句,首先,程序本身为什么用迭代而不是递归。其实我在构思程序框架的时候,就隐约感觉到可以用迭代方式。而所谓数独游戏,说白了不过就是一个有关排列组合的数学题,既然是排列组合,就牵扯到暴力搜索,而一般暴力搜索除了递归方式外,迭代也可以胜任。考虑到函数调用的堆栈开销和数独本身恐怖的81层深度,所以我最后选择了迭代方式。【最后不得不说的是,算法的效率比我本身预期快的多,估计用递归也查不到哪去】
其次,在随机生成初始化谜题时,如果运气不好,有可能会生成一个无解的死局,没关系,程序会发现并自动生成新的谜题。
最后,细心的朋友可能会注意到【看那一段被注释掉的代码】——我写这个程序最初的目的是让它帮我解决某个特定的问题,而不是现在随机的制造问题。有关解决某个特定问题的版本,我们暂且称之为数独作弊器吧,其实我也做出来了,当初因为自己的能力有限,作弊器写好的代码无法运行,然后通过查阅资料和求助大牛,最终解决了这个问题,这是一个有关老版本scanf函数如何兼容新IDE的故事,其中内容,我会另开一片博文。
不知道csdn能否上传小附件,反正我找了半天没找到,算了,如果有朋友对本程序感兴趣,可以问我要可执行文件,谢谢。