大家知道数独吗?
九宫格数独,是一种源自18世纪末的瑞士,后在美国发展、并在日本得以发扬光大的数字谜题。数独盘面是个九宫,每一宫又 分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一 宫中都只出现一次。这种游戏全面考验做题者观察能力和推理能力,虽然玩法简单,但数字排列方式却千变万化,所以不少教育者认为数独是训练头脑的绝佳方式
刚开始接触这种戏的时候,我是茶饭不思,一天的时间将我那本书上所有难度的题目全部解决,最长的一个花了将近3小时的时间。后来觉得自己用手算没意思,便想用c语言写一个程序还解数独。不过,那时的我“功力尚浅”,自然没能解决这个问题。
最近在学算法的时候,看到了回溯的那一章。自己认认真真的想了一下回溯的过程,发现与解数独很类似:自己手算数独遇到一个空有两种可能,我假设其中一个成立,推理下去。如果后面遇到矛盾就退回来,另外一个可能肯定就是了。
好了,我思考了一下。将解数独的伪代码写了出来:
</pre> 如果填好的数是81,就代表问题解决,打印结果 否则: 选择一个没有填数的位置 For(i = 1;i < 10;i++){ 如果i能填入这里(利用数独规则) 填入i,进入下一次回溯 恢复回溯前的状态 否则 Continue } <pre>
回溯使用了递归,所以整个程序很简洁。将上面的源代码转换成c代码如下:
int solve(int times) { int line,row;//代填数字的位置 int i; if(81 == sd.achieve_number){//得到一个解,打印出来,返回函数 printf("We find an answer.\n"); printAnswer(); answer_state = 1;//我找到解了 return 1; } else{ //选择一个合适的空格 findPosition(&line,&row); //试填数字 for(i = 1;i < 10;i++){ //测试这个空格是否可以填i if(!testThisNumber(line,row,i)){//可以就填入i,更新数据,进入下一次回溯 //尝试i 然后进入下一次回溯 tryIt(line,row,i); if(solve(times++)) return 1; //恢复数据到填入i之前 retryIt(line,row,i); } else//不可以就试下一个数字 continue; } } return 0; }
这只是主程序的代码,我放在solve.c中,中间用到的几个函数,代码也很简单。全部放在solve.c中。这里我只是找到一个解就返回程序了,因为多解的情况太复杂了,需要的时间也太多了。所以我只打印一个解。这里还要说一下,我将数独放在下面的数据结构中:
typedef struct SoDu{ int data[10][10];//保存数独的81个数字 下标从1开始 int line_number[10][10];//统计第几行的i是否填好 int row_number[10][10]; int small_number[10][10]; int achieve_number;//统计已经填好的数字数 }*psodu;
我将数独题目放在一个10*10的二维数组中int data[10][10]. 为了方便下标都是从1开始。line_number二维数组表示第几行的哪个数字是否已经填好(用1表示).例如line_number[2][5]它的 值表示第二行的数字5是不是已经填好了。Row_number的作用一样。这里值得注意的是small_number数组,它表示的每个3*3小九宫格的 9个数字的填写情况(数独规则之一)。我还定义了一个宏将一个行列位置转换成small:
#define TOSMALL(line,row) ((line) - 1)/3 * 3 + ((row) - 1)/3 + 1
整个程序用一个“超级全局”变量sd交流。这面这些都放在了data.h头文件中。
数独的题目预先放在一个文件中,文件保存了81个数字,数字0表示这个位置没有填数,像下面这样:
0 5 0 0 3 0 0 0 0
0 0 1 6 0 0 9 0 8
3 4 0 7 0 1 0 0 0
0 0 0 0 0 7 0 0 5
0 9 0 0 0 0 0 8 0
7 0 0 2 0 0 0 0 0
0 0 0 3 0 9 0 6 1
6 0 4 0 0 8 3 0 0
0 0 0 0 6 0 0 7 0
这样就表示了一个数独题目。至于怎么将题目读进sd中,我使用了两个函数readFile()与initSodu()。前者读取文件的81个数字,后者将读到的81个数字初始化sd。这两个函数的定义在init.c文件中。
好了,现在所有的“零件”都做好了,只差“组装”了。下面就是我在main.c中的“组装”:
int a[81]; int main() { readFile("test.txt",a); initSodu(a,&sd); solve(0); if(0 == answer_state) printf("No answer.\n"); getch(); return 0; }
至此,整个程序就写完了。我使用test.txt(内容就是上面的那个题目)
速度上,我没测试。但是也还可以(因为算法只是简单的回溯,我没有做任何优化。之后等学完了算法的内容之后我会在优化这个程序,目前只是我原了自己的一个小小的“心愿” :)。
希望能对你有所帮助。同时如果你有什么新的看法,欢迎与我分享。期待! :)
(全文完)
若非注明,均为原创文章,转载请注明: 转载自大笨兔博客 | C语言编程 | web开发 |趣味数学
本文链接地址: C语言:回溯解数独程序
转载于:https://blog.51cto.com/dabentu/932813