数独的生成和破解算法分析


  最近在捉摸数独的破解方法,自己本不是搞软件的,而是电子的。所以虽然写出来了一个,但是方法很笨拙。 在网上查好时,发现了有一种算法的思维与众不同,既简单又高效,不像其他算法一样递归的太深。

     我对作者的代码分析了下,并且给出了点注释。 原网址是:http://blog.pfan.cn/rickone/22806.html

     

     作者的核心思想是她/他说的:

核心算法:深度优先搜索(其它形式的搜索也可以)

数据结构:如果用递归的形式写深搜,定义在函数dfs里的所有变量都可以看成是这里的数据结构,因为它们自动地被系统压入栈内,所以,省了,你唯一要做的就是一个二维数组,存放当前数独的状态。

当然有了这些,偶还不敢动手做,如果你做过马遍历的程序,大概会有点怕,那才8x8,这里是9x9,不来点‘启发式’谁敢动手写程序,有可能一个数独来几千几万个解,一个解要搜80层上下(估计),懂得树这个数据结构的人就会明白,80层是什么概念,1-9有9个数字,就是9叉树,至少是9^80量级的代价,什么?计算机反正算得快?也不行,再快的计算机遇到指数复杂度的程序也得变回傻子!谢天谢地,棋局尺寸是固定不变的,我们需要做的就是,剪枝。

偶的启发式思想来源于偶想数独的思路,数独之所以难是因为可行情况太多,把这种不确定性降低就会使它变得简单。某个格子上可以填的数字的个数就称为它的不确定度吧,首先,如果一开始空格比较少,那空格上的不确定度就小,数独也就相对容易,同时,随着填上去数字增多,剩余空格上的不确定度也会降低,如果某个格子上的不确定度降到1,那这个格子可以先确定下来,如果降到了0,哦,非常遗憾,在前面的填数中一定是填错了,剪枝也发生在这里,你不得不回退。

详细地说,如果把每个空格做为分枝,那要优先选择哪个分枝进行搜索呢?对每个空格计算它的权值,也就是它的不确定度,然后从中选出最小的一个进行搜索,同时放弃其它空格在这一层上的搜索!也就是说对剩余的空格交给子层处理。这样在某一个结点上的处理就包括两步:1,选择最佳空格,2,遍历这个空格的所有可行值,填入空格,递归。

   生成数独的主要代码是: 

 

 CSudoku::CSudoku(int n)//参数n表示了需要生成的数独的复杂程度
{
 int i,j,k,m,mark[10],temp,blanks=0;//蓝色区域代码是用于生成数独
 srand(time(0));
 for(i=0;i<9;++i)
 {
  for(j=0;j<9;++j)
   map[i][j]=j+1;
  for(j=0;j<9;++j)
  {
   int a=rand()%9;
   int b=rand()%9;
   temp=map[i][a];
   map[i][a]=map[i][b];
   map[i][b]=temp;
  }
 }
 for(i=0;i<9;++i)
 {
  for(j=1;j<=9;++j)
   mark[j]=0;
  for(j=0;j<9;++j)
  {
   if(mark[map[j][i]])
   {
    for(k=8;k>=0;--k)
    {
     if(mark[map[j][k]]==0)
     {
      temp=map[j][i];
      map[j][i]=map[j][k];
      map[j][k]=temp;
      break;
     }
    }
   }
   else
   {
    mark[map[j][i]]=1;
   }
  }
 }
 for(i=0;i<9;++i)
 {
  for(j=1;j<=9;++j)
   mark[j]=0;
  for(j=8;j>=0;--j)
  {
   if(mark[map[j][i]])
   {
    map[j][i]=0;
    blanks++;
   }
   else
    mark[map[j][i]]=1;
  }
 }
 for(i=0;i<9;i+=3)
 {
  for(j=0;j<9;j+=3)
  {
   for(k=1;k<=9;++k)
    mark[k]=0;
   for(k=0;k<3;++k)
   {
    for(m=0;m<3;++m)
    {
     if(map[i+k][j+m]==0)
      continue;
     if(mark[map[i+k][j+m]])
     {
      map[i+k][j+m]=0;
      blanks++;
     }
     else
      mark[map[i+k][j+m]]=1;
    }
   }
  }
 }
 while(n>blanks)
 {
  m=rand()%81;
  i=m/9;
  j=m%9;
  if(map[i][j]>0)
  {
   map[i][j]=0;
   blanks++;
  }
 }
 printf("(randomized sudoku created with %d blanks.)\n",blanks);
}
 

  对于破解数独代码:

CSudoku::CSudoku(int *data)//根据data初始化一个map[][]
{
 int *pm=(int*)map;
 for(int i=0;i<81;++i)
  pm[i]=data[i];
}
CSudoku::~CSudoku()
{
 return;
}
void CSudoku::display()//函数用于打印显示数独
{
 for(int i=0;i<9;++i)
 {
  for(int j=0;j<9;++j)
  {
   if(map[i][j]>0)
    printf("< %d >  ",map[i][j]);
   else
    printf("[   ]  ");
  }
  printf("\n");
 }
}
void CSudoku::resolve()//用于解数独的主要切入口函数
{
 solves=0;
 dfs();
 if(solves==0)
  printf("(sorry,this sudoku is a bad one.)\n");
}
int CSudoku::check(int y,int x,int *mark)//计算(y,x)处格子的不确定度
{
 int i,j,is,js,count=0;
 for(i=1;i<=9;++i)
  mark[i]=0;//初始化
 for(i=0;i<9;++i)
  mark[map[y][i]]=1;//计算该行中已经确定的值对应的mark[i]的变量值为1
 for(i=0;i<9;++i)
  mark[map[i][x]]=1;//计算该列中确定的值
 is=y/3*3;
 js=x/3*3;
 for(i=0;i<3;++i)//计算九格里面的确定的值
 {
  for(j=0;j<3;++j)
   mark[map[is+i][js+j]]=1;
 }
 for(i=1;i<=9;++i)
  if(mark[i]==0)
   count++;
 return count;
}
void CSudoku::dfs()//执行的主要函数
{
  	if(solves>=5)//这两句是我自己加的,主要是控制结果的数量。因为一个数独可能有几百种结果,而我们不需要这么多,而且递归次数越多耗时越多
		return;
 int i,j,im=-1,jm,min=10;
 int mark[10];
 for(i=0;i<9;++i)
 {
  for(j=0;j<9;++j)
  {
   if(map[i][j])//如果不为零时,不确定度为0,不需要计算不确定度了
    continue;
   int c=check(i,j,mark);//用于计算不确定度
   if(c==0)//表示不确定度为0,前面的数字填错了,或者是说这个数组本身就存在问题
    return;
   if(c<min)//用于计算最小的不确定度,从最小的不确定度开始搜索
   {
    im=i;
    jm=j;
    min=c;
   }
  }
 }
 if(im==-1)//这是当所有格子里面的数值都确定时显示结果
 {
  printf("No. %d:\n",++solves);
  display();
  return;
 }
 check(im,jm,mark);
 for(i=1;i<=9;++i)
 {
  if(mark[i]==0)
  {
   map[im][jm]=i;//将所有可能的数值都赋值一次
   dfs();
  }
 }
 map[im][jm]=0;//如果赋的值不满足条件就把这个格子归零,以便于后面的搜索
}
#include <iostream>
#include "sudoku.h"
using namespace std;
int main()
{
 int data1[]=
 {4,9,0,0,0,6,0,2,7,
  5,0,0,0,1,0,0,0,4,
  6,0,0,0,0,8,0,0,3,
  1,0,4,0,0,0,0,0,0,
  0,6,0,0,0,0,0,5,0,
  0,0,0,0,0,0,2,0,8,
  7,0,0,2,0,0,0,0,5,
  8,0,0,0,9,0,0,0,1,
  3,4,0,5,0,0,0,6,2
 };
 int data2[]=
 {7,4,0,0,8,0,0,1,6,
  9,0,0,0,3,5,0,0,4,
  0,0,0,7,0,0,0,0,0,
  0,7,0,0,0,9,5,0,0,
  6,1,0,0,5,0,0,8,7,
  0,0,2,6,0,0,0,4,0,
  0,0,0,0,0,4,0,0,0,
  3,0,0,5,6,0,0,0,2,
  5,6,0,0,1,0,0,3,9
 };
 CSudoku s1(data1);
 s1.display();
 s1.resolve();
 CSudoku s2(data2);
 s2.display();
 s2.resolve();
 return 0;
}

  作者后面还给出了改进的算法,就不加以介绍了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值