问题描述:
数独是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个九宫格内的数字均含1-9,不重复
要求:设计算法随机生成不同难度的数独游戏,阐述如何评价所生成数独的难度。
我的分析思路:
对于数独程序,主要需要解决两个问题:数独生成、数独求解。目前影响比较大,功能比较完善的数独程序有:Hodoku、Sudoku Explainer。
要实现数独的生成,就要先理清每一个位置的约束条件,每个位置的数需要满足四个约束条件:
首先,这个格子里不能有数字,即为空
其次,同一行不能有相同数字
再次,同一列不能有相同数字
最后,同一个九宫格不能有相同数字
用算法的形式把这四个约束条件表现出来,就是数独的生成。
计算机程序求解数独,一般采用回溯法,对于任意数独初盘,好的算法都可以在一秒内得到解。目前,非常有效的算法,是舞蹈链(Dancing Links)算法。它实际上也是一种回溯算法,巧妙地运用了双向十字链表的数据结构,用空间换取时间,将数独求解转化为一个精确覆盖问题,用C语言实现的算法,在普通的微机上,能够在0.1ms左右对任意标准数独进行求解。
大致步骤:
数独求解:
首先,要先确定该数独中空格的数量以及位置,用count记录空格的数量,用blank数组记录所有需要填的空格的坐标。
然后,定义一个初始坐标top,使初始坐标top=0,利用一个双循环求解。
进行第二步后,会有一部分空格需要填的数不唯一,因此需要进行试填,从而使数独能更好的解决。定义a,b记录当前top指向的元素作为当前试填空格的坐标,利用循环从1-9依次试填并检验当前空格内填入的数字是否可行,我们定义一个变量ok表示当前空格内填入的数字是否可行,当ok=1时表示可行,ok=0时表示不可行。如果填入的数字可行,top+1进行下一个空格的试填,最终求解。
#include <bits/stdc++.h>
bool fill(int sd[][9])
{
int count = 0; //count用于记录需要填的空格数量,初始为0
int blank[81][2]; //blank数组用于记录所有需要填的空格的坐标
for(int i = 0;i < 9;i++)
for(int j = 0;j < 9;j++) //
if(sd[i][j] == 0) //
{ // 将整个数独扫描一遍,
blank[count][0]=i; // 统计其中的空格数量
blank[count][1]=j; //
count++; //
}
int top = 0; //top指向blank数组元素,表示当前待填坐标。初始为0
while(top != -1) //最外层循环,每填一个空格,top加1,每回退一步,top减1,如果top==-1,循环结束
{
if(top == count) //因为每填完一个空格后top加1,填最后一个空格时top==count-1,填完后top加1即等于count
{
printf("最终结果为:\n");
for(int s = 0;s < 9;s++) //
{ //
for(int t=0;t<9;t++) //
{ // 输出一个解
printf("%d ",sd[s][t]); //
} //
printf("\n"); //
}
top--; //如果找到一个解,回退一步,找其他解
continue; //只要发生回退,就回到循环开头测试循环条件top != -1,不会往下执行
}
int a = blank[top][0]; //如果执行到这里,说明还有空格要填,
int b = blank[top][1]; //用a,b记录下top指向的元素作为当前试填空格的坐标
int n = sd[a][b]; //用n记录当前空格中的数字,这个数字作为试填的“进度”,从这个数字加1往下试填
for(n++;n <= 9;n++) //将n加1后,一直循环到9进行试填
{
int ok = 1; //ok=1标志当前数字可行
for(int k = 0;k < 9;k++) //检测同行,同列,同块的数字是否有相同的
{
if(sd[a][k] == n || sd[k][b]== n || sd[(a / 3) * 3 + k / 3][(b/ 3) * 3 + k % 3] == n ) //同行,同列,同块的数字一起检测
{
ok = 0; //如果同行,同列,同块某一处有相同数字,则ok=0,表示当前数字不可行
break; //不再往下检测,跳出此层循环,继续试下一个数字
}
}
if(ok) break; //如果ok保持为1,说明当前数字可行(否则肯定会被修改为0),跳出循环,不再往下试
}
if(n <= 9) // 如果有一个数字可行,n<=9。否则,n=10
{
sd[a][b] = n; //将这个数字填入空格中
top++; //top加1,处理下一个空格
}
if(n > 9) //如果试到9都不行的话,循环结束后n的值为10,此时要回退
{
sd[a][b] = 0; //回退时先将当前空格中数字改为0,相当于清零
top--; //回退1步
}
}
}
int main(void)
{
int sd[9][9] = { // “世界最难数独”信息
8,0,0,0,0,0,0,0,0,
0,0,3,6,0,0,0,0,0,
0,7,0,0,9,0,2,0,0,
0,5,0,0,0,7,0,0,0,
0,0,0,0,4,5,7,0,0,
0,0,0,1,0,0,0,3,0,
0,0,1,0,0,0,0,6,8,
0,0,8,5,0,0,0,1,0,
0,9,0,0,0,0,4,0,0,
};
fill(sd); //调用函数求解
return 0;
}