大家知道数独吗?

九宫格数独,是一种源自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(&amp;line,&amp;row);
    //试填数字
     for(i = 1;i &lt; 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,&amp;sd);
    solve(0);
    if(0 == answer_state)
        printf("No answer.\n");
    getch();
    return 0;
}

至此,整个程序就写完了。我使用test.txt(内容就是上面的那个题目)

速度上,我没测试。但是也还可以(因为算法只是简单的回溯,我没有做任何优化。之后等学完了算法的内容之后我会在优化这个程序,目前只是我原了自己的一个小小的“心愿” :)。

×××

希望能对你有所帮助。同时如果你有什么新的看法,欢迎与我分享。期待! :)

(全文完)

若非注明,均为原创文章,转载请注明: 转载自大笨兔博客 | C语言编程 | web开发 |趣味数学

本文链接地址: C语言:回溯解数独程序