转载自 CK博客
数独:
独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。
数独(计算器)所要实现的功能:
在九宫格内用户随机填入数字,计算器给出一个解。
数独(计算器)算法思路:
利用回溯法:维基百科给出的定义
回溯法(英语:)是暴力搜寻法中的一种。
回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:
- 找到一个可能存在的正确的答案
- 在尝试了所有可能的分步方法后宣告该问题没有答案
在最坏的情况下,回溯法会导致一次复杂度为指数时间的计算。
这种算法,从字面意思上理解,很接近于人的思想。
但是,就数独来说,如果采取每一个格子都试1~9的话,也就是9的81次方(81个1试过才知道是错的)可试完所有的情况,即使是“天河2号”也得算到宇宙毁灭。即上面的最坏的情况
根据数独的规则-“独”:
1.我们就有了方法是check().用来检查九宫格是否符合规则。
2.我们至少要排除一些基本的错误答案,那就有了方法是seach().用来查找当前空格表面上可以填的数字。
3.我们要尝试填数字并且这个方法是递归的test()。
很显然,test()方法会先利用seach()查找这一格所能填的数字,然后填数字,递归填数字,直到九宫格填满(计算成功),或某一格没有数字可填,即遇到错误,需要取消上一步,使上一步填另一个数字。
基本思路是这样。再做一些优化,数据结构上的优化。
下面程序是我用MFC改写大神们的程序,若有结构算法上的不足,还望高手指点一二。
在此详解一下算法类CSudokuSolution
class CSudokuSolution
{public:
CSudokuSolution(int *data);
~CSudokuSolution(void);public:
BYTE m_data[9][9];//九宫格
bool check(int x,int y); //检查是否符合规则
int search(int x,int y,int sch[10]); //搜索可填数,sch参数是可填数数组
void fill(int time); //填数,time参数来判断是否超时};
/*****************************************
* 函数功能:检查指定位置的数是否符合规则
* 参数:x 第一维坐标
* y 第二维坐标
* 返回值: false 不符合规则
* true 符合规则
******************************************/
bool CSudokuSolution::check(int x,int y)
{if(m_data[x][y]==0)return true;//0被默认为未填
if(m_data[x][y]>9||m_data[x][y]<1)return false;//不允许超过1~9的范围for(int i=0;i<9;i++)
if(m_data[x][y]==m_data[i][y]&&(i!=x))return false;//列检查
for(int j=0;j<9;j++)
if(m_data[x][y]==m_data[x][j]&&(j!=y))return false;//行检查
for(int i=x/3*3;i<x/3*3+3;i++)
for(int j=y/3*3;j<y/3*3+3;j++)
if(m_data[x][y]==m_data[i][j]&&(i!=x)&&(j!=y))return false;//3×3范围检查
return true;
}
/*****************************************
* 函数功能:查找指定位置的可填符合规则的数
* 参数:x 第一维坐标
* y 第二维坐标
* sch 存储可填数
* 返回值: 可填数的个数
******************************************/
int CSudokuSolution::search(int x,int y,int sch[10])
{int temp[10]={0,1,1,1,1,1,1,1,1,1};//可填数的标记数组,0标记为不可填,1标记为可填。
int iCount=0;for(int i=0;i<9;i++)
if(m_data[i][y])temp[m_data[i][y]]=0;//列检查
for(int j=0;j<9;j++)
if(m_data[x][j])temp[m_data[x][j]]=0;//行检查
for(int i=x/3*3;i<x/3*3+3;i++)
for(int j=y/3*3;j<y/3*3+3;j++)
if(m_data[i][j])temp[m_data[i][j]]=0;//3×3范围检查
for(int i=0;i<10;i++)
iCount+=temp[i];//记录剩余的标记“1”,就相当于可填数的个数
for(int i=0;i<10;i++)
sch[i]=temp[i];//将可填数存到数组中对应位置
return iCount;
}
/*****************************************
* 函数功能:回溯算法的试探函数
* 参数:time 系统滴答时钟计数,用于比较运算时间
******************************************/
void CSudokuSolution::fill(int time)
{int now=GetTickCount();//获得时间计数
if(now-time>10000)throw(-1);//超时抛错
int sch[10]={0};
int iCount;
int x_Min,y_Min;//可填数个数最少的位置的坐标
int iMin=10;//最少可填数个数
//查找9×9范围内,最少可填数的位置
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
{
if(!m_data[i][j])
{
iCount=search(i,j,sch);
if(iCount==0)return;
if(iCount<iMin)
{
iMin=iCount;
x_Min=i;
y_Min=j;
}
}
}
//所有位置填满,抛出succuss
if(iMin==10)throw(1);
//否则进行试探填数
search(x_Min,y_Min,sch);
//下面的循环实现 回溯最主要部分
for(int i=0;i<10;i++)
{
if(sch[i])//可填数标记
{
//若标记“1”,则填当前可填数,并递归
m_data[x_Min][y_Min]=i;
fill(time);
}
}
m_data[x_Min][y_Min]=0;//若返回,则清零(未填)当前位置
}
附上源码:SudokuMFC-CK.zip