• 数独(sudoku)的生成与破解(转)


    数独(sudoku)的生成与破解(发表时间: 2007-1-25 21:06:00)

     本文链接:http://blog.pfan.cn/rickone/22806.html 复制链接

     

    数独(sudoku)的生成与破解
    最近在网上比较流行的智力游戏。笔者本人也玩过,可以下个模拟游戏试试,简单的还可以,太难就无从下手了。虽然偶脑子不好使,但偶是计算机科班出身,怕你不成,老规矩,编程破解。

    首先,偶是第一次做数独的程序,可能程序不强,以后有时间再改进改进。望高手评析。

    还是把数独游戏的规则说一说吧,或许你是刚刚听说这个名字的朋友。数独(sudoku),起源于瑞士,于1970 年代由美国的一家数学逻辑游戏杂志首先发表,当时名为Number Place。及后在日本大力推广下得以发扬光大,于1984 年取名“数独”,即“独立的数字”的省略,在一个9x9的方格中,有81个小方格组成,然后又分9个大块,每块由3x3的方格组成,就是中国的九宫图,大九宫里面再套9个小九宫,共九九八十一个小格子,游戏开始前会有一些格子上写好了数,你需要在剩下的格子里填数,真到把所有格子填满,并且要求,任何一行或一列或者一个小九宫中没有相同的数字,当然你只能用1-9之间的9个数字。如下图就是一个数独。希望我解释清楚了,如果你还不清楚,用google搜索一下相关资料,点这里http://www.google.com/search?q=sudoku%20%E6%95%B0%E7%8B%AC&hl=zh-CN&lr=&nxpt=10.1471893728569764719035

    好啦,言归正传。简单来说,我的破解法非常简单,就是超级无敌强硬大搜索,呵呵,别拿你想数独的那套来让计算机想,它比你可笨得多了,它只会照程序做事,做得快而已,快就是它的本事,其它的一概不会。

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

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

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

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

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

    OK,看程序:

      1 #ifndef SUDOKU_RICK_0701_
      2 #define SUDOKU_RICK_0701_
      3 class CSudoku
      4 {
      5  int map[9][9];
      6  int solves;
      7  int check(int,int,int*);
      8  void dfs();
      9 public:
     10  CSudoku(int n=40);// 随机生成数独,n越大越难
     11  CSudoku(int *data);// 人工指定数独
     12  virtual ~CSudoku();
     13  void display();// 输出数独
     14  void resolve();// 解数独
     15 };
     16 #endif
     17 
     18 #include "sudoku.h"
     19 #include "stdio.h"
     20 #include "stdlib.h"
     21 #include "time.h"
     22 
     23 CSudoku::CSudoku(int n)
     24 {
     25  int i,j,k,m,mark[10],temp,blanks=0;
     26  srand(time(0));
     27  for(i=0;i<9;++i)
     28  {
     29   for(j=0;j<9;++j)
     30    map[i][j]=j+1;
     31   for(j=0;j<9;++j)
     32   {
     33    int a=rand()%9;
     34    int b=rand()%9;
     35    temp=map[i][a];
     36    map[i][a]=map[i][b];
     37    map[i][b]=temp;
     38   }
     39  }
     40  for(i=0;i<9;++i)
     41  {
     42   for(j=1;j<=9;++j)
     43    mark[j]=0;
     44   for(j=0;j<9;++j)
     45   {
     46    if(mark[map[j][i]])
     47    {
     48     for(k=8;k>=0;--k)
     49     {
     50      if(mark[map[j][k]]==0)
     51      {
     52       temp=map[j][i];
     53       map[j][i]=map[j][k];
     54       map[j][k]=temp;
     55       break;
     56      }
     57     }
     58    }
     59    else
     60    {
     61     mark[map[j][i]]=1;
     62    }
     63   }
     64  }
     65  for(i=0;i<9;++i)
     66  {
     67   for(j=1;j<=9;++j)
     68    mark[j]=0;
     69   for(j=8;j>=0;--j)
     70   {
     71    if(mark[map[j][i]])
     72    {
     73     map[j][i]=0;
     74     blanks++;
     75    }
     76    else
     77     mark[map[j][i]]=1;
     78   }
     79  }
     80  for(i=0;i<9;i+=3)
     81  {
     82   for(j=0;j<9;j+=3)
     83   {
     84    for(k=1;k<=9;++k)
     85     mark[k]=0;
     86    for(k=0;k<3;++k)
     87    {
     88     for(m=0;m<3;++m)
     89     {
     90      if(map[i+k][j+m]==0)
     91       continue;
     92      if(mark[map[i+k][j+m]])
     93      {
     94       map[i+k][j+m]=0;
     95       blanks++;
     96      }
     97      else
     98       mark[map[i+k][j+m]]=1;
     99     }
    100    }
    101   }
    102  }
    103  while(n>blanks)
    104  {
    105   m=rand()%81;
    106   i=m/9;
    107   j=m%9;
    108   if(map[i][j]>0)
    109   {
    110    map[i][j]=0;
    111    blanks++;
    112   }
    113  }
    114  printf("(randomized sudoku created with %d blanks.)\n",blanks);
    115 }
    116 CSudoku::CSudoku(int *data)
    117 {
    118  int *pm=(int*)map;
    119  for(int i=0;i<81;++i)
    120   pm[i]=data[i];
    121 }
    122 CSudoku::~CSudoku()
    123 {
    124  return;
    125 }
    126 void CSudoku::display()
    127 {
    128  for(int i=0;i<9;++i)
    129  {
    130   for(int j=0;j<9;++j)
    131   {
    132    if(map[i][j]>0)
    133     printf("< %d >  ",map[i][j]);
    134    else
    135     printf("[   ]  ");
    136   }
    137   printf("\n");
    138  }
    139 }
    140 void CSudoku::resolve()
    141 {
    142  solves=0;
    143  dfs();
    144  if(solves==0)
    145   printf("(sorry,this sudoku is a bad one.)\n");
    146 }
    147 int CSudoku::check(int y,int x,int *mark)
    148 {
    149  int i,j,is,js,count=0;
    150  for(i=1;i<=9;++i)
    151   mark[i]=0;
    152  for(i=0;i<9;++i)
    153   mark[map[y][i]]=1;
    154  for(i=0;i<9;++i)
    155   mark[map[i][x]]=1;
    156  is=y/3*3;
    157  js=x/3*3;
    158  for(i=0;i<3;++i)
    159  {
    160   for(j=0;j<3;++j)
    161    mark[map[is+i][js+j]]=1;
    162  }
    163  for(i=1;i<=9;++i)
    164   if(mark[i]==0)
    165    count++;
    166  return count;
    167 }
    168 void CSudoku::dfs()
    169 {
    170  int i,j,im=-1,jm,min=10;
    171  int mark[10];
    172  for(i=0;i<9;++i)
    173  {
    174   for(j=0;j<9;++j)
    175   {
    176    if(map[i][j])
    177     continue;
    178    int c=check(i,j,mark);
    179    if(c==0)
    180     return;
    181    if(c<min)
    182    {
    183     im=i;
    184     jm=j;
    185     min=c;
    186    }
    187   }
    188  }
    189  if(im==-1)
    190  {
    191   printf("No. %d:\n",++solves);
    192   display();
    193   return;
    194  }
    195  check(im,jm,mark);
    196  for(i=1;i<=9;++i)
    197  {
    198   if(mark[i]==0)
    199   {
    200    map[im][jm]=i;
    201    dfs();
    202   }
    203  }
    204  map[im][jm]=0;
    205 }
    206 
    207 #include <iostream>
    208 #include "sudoku.h"
    209 using namespace std;
    210 int main()
    211 {
    212  int data1[]=
    213  {4,9,0,0,0,6,0,2,7,
    214   5,0,0,0,1,0,0,0,4,
    215   6,0,0,0,0,8,0,0,3,
    216   1,0,4,0,0,0,0,0,0,
    217   0,6,0,0,0,0,0,5,0,
    218   0,0,0,0,0,0,2,0,8,
    219   7,0,0,2,0,0,0,0,5,
    220   8,0,0,0,9,0,0,0,1,
    221   3,4,0,5,0,0,0,6,2
    222  };
    223  int data2[]=
    224  {7,4,0,0,8,0,0,1,6,
    225   9,0,0,0,3,5,0,0,4,
    226   0,0,0,7,0,0,0,0,0,
    227   0,7,0,0,0,9,5,0,0,
    228   6,1,0,0,5,0,0,8,7,
    229   0,0,2,6,0,0,0,4,0,
    230   0,0,0,0,0,4,0,0,0,
    231   3,0,0,5,6,0,0,0,2,
    232   5,6,0,0,1,0,0,3,9
    233  };
    234  CSudoku s1(data1);
    235  s1.display();
    236  s1.resolve();
    237  CSudoku s2(data2);
    238  s2.display();
    239  s2.resolve();
    240  return 0;
    241 }

    代码里有很大部分实际上是在‘生成数独’,然后结果并不是我所料的那样,我用随机填充的方法,生成的大都是没有解的数独,如果空格太多,解是有,也太多。所以数独的生成似乎成了个难题。

    数独的生成,最好的情况是,先得到一个完整的数独,然后根据难度需要,随机地挖一些空格出来,过程是相反的,所以可以保证有解。所以关键就是如何得到完整的数独,也要有一定的随机化。用上面的程序可以试验出来,如果我挖的空格越大,就越容易出解(相对于无解的情况),那偶就可以从一些有很多空格的数独出发,用解数独的程序,先解一个数独,那不就得到了完整的数独!也就是先只设定少数一些位置上的数字,然后用解数独程序得到完整数独,然后再挖一些空格出来,这样就得到一个绝对有解的数独,that's right!

    偶的生成方法采用很简单的方法,因为可以通过上面的程序验证,当有72个空格时,可以很快得到一个数独解,偶就在一开始在每一行的随机位置上填上1-9的数字,这些初始数字就叫他们种子吧,不同的种子可以得到不同的数独,9行,每行9个位置,那有9的9次方,大概3亿多个不同情况,那我至少可以得到3亿多个不同的完整数独,再随机去掉不同数目的空格,那就可以生成相当数量的数独了!

    改进了的数独生成程序及完整的数独代码:(比原来更短了)

      1 #ifndef SUDOKU_RICK_0701_
      2 #define SUDOKU_RICK_0701_
      3 class CSudoku
      4 {
      5  int map[9][9];
      6  int smod;
      7  int solves;
      8  int check(int,int,int*);
      9  void dfs();
     10 public:
     11  enum{ANY=0,ALL=1};
     12  CSudoku(int n=40);// 随机生成数独,n越大越难
     13  CSudoku(int *data);// 人工指定数独
     14  virtual ~CSudoku();
     15  void display();// 显示数独
     16  int resolve(int mod=ALL);// 解数独
     17 };
     18 #endif
     19 
     20 #include "sudoku.h"
     21 #include "stdio.h"
     22 #include "stdlib.h"
     23 #include "time.h"
     24 
     25 CSudoku::CSudoku(int n)
     26 {
     27  int i,j;
     28  srand(time(0));
     29  do
     30  {
     31   for(i=0;i<9;++i)
     32   {
     33    for(j=0;j<9;++j)
     34     map[i][j]=0;
     35    j=rand()%9;
     36    map[i][j]=i+1;
     37   }
     38  }
     39  while(!resolve(ANY));
     40 
     41  // 挖窟窿
     42  for(int k=0;k<n;)
     43  {
     44   i=rand()%81;
     45   j=i%9;
     46   i=i/9;
     47   if(map[i][j]>0)
     48   {
     49    map[i][j]=0;
     50    ++k;
     51   }
     52 
     53  }
     54  //printf("(randomized sudoku created with %d blanks.)\n",blanks);
     55 }
     56 CSudoku::CSudoku(int *data)
     57 {
     58  int *pm=(int*)map;
     59  for(int i=0;i<81;++i)
     60   pm[i]=data[i];
     61 }
     62 CSudoku::~CSudoku()
     63 {
     64  return;
     65 }
     66 void CSudoku::display()
     67 {
     68  for(int i=0;i<9;++i)
     69  {
     70   for(int j=0;j<9;++j)
     71   {
     72    if(map[i][j]>0)
     73     printf("< %d >  ",map[i][j]);
     74    else
     75     printf("[   ]  ");
     76   }
     77   printf("\n");
     78  }
     79 }
     80 int CSudoku::resolve(int mod)
     81 {
     82  smod=mod;
     83  if(mod==ALL)
     84  {
     85   solves=0;
     86   dfs();
     87   return solves;
     88  }
     89  else if(mod==ANY)
     90  {
     91   try
     92   {
     93    dfs();
     94    return 0;
     95   }
     96   catch(int)
     97   {
     98    return 1;
     99   }
    100  }
    101  return 0;
    102 }
    103 int CSudoku::check(int y,int x,int *mark)
    104 {
    105  int i,j,is,js,count=0;
    106  for(i=1;i<=9;++i)
    107   mark[i]=0;
    108  for(i=0;i<9;++i)
    109   mark[map[y][i]]=1;
    110  for(i=0;i<9;++i)
    111   mark[map[i][x]]=1;
    112  is=y/3*3;
    113  js=x/3*3;
    114  for(i=0;i<3;++i)
    115  {
    116   for(j=0;j<3;++j)
    117    mark[map[is+i][js+j]]=1;
    118  }
    119  for(i=1;i<=9;++i)
    120   if(mark[i]==0)
    121    count++;
    122  return count;
    123 }
    124 void CSudoku::dfs()
    125 {
    126  int i,j,im=-1,jm,min=10;
    127  int mark[10];
    128  for(i=0;i<9;++i)
    129  {
    130   for(j=0;j<9;++j)
    131   {
    132    if(map[i][j])
    133     continue;
    134    int c=check(i,j,mark);
    135    if(c==0)
    136     return;
    137    if(c<min)
    138    {
    139     im=i;
    140     jm=j;
    141     min=c;
    142    }
    143   }
    144  }
    145  if(im==-1)
    146  {
    147   if(smod==ALL)
    148   {
    149    printf("No. %d:\n",++solves);
    150    display();
    151    return;
    152   }
    153   else if(smod==ANY)
    154   {
    155    throw(1);
    156   }
    157  }
    158  check(im,jm,mark);
    159  for(i=1;i<=9;++i)
    160  {
    161   if(mark[i]==0)
    162   {
    163    map[im][jm]=i;
    164    dfs();
    165   }
    166  }
    167  map[im][jm]=0;
    168 }
    169 
    170 #include <iostream>
    171 #include "sudoku.h"
    172 using namespace std;
    173 int main()
    174 {
    175  int data1[]=
    176  {4,9,0,0,0,6,0,2,7,
    177   5,0,0,0,1,0,0,0,4,
    178   6,0,0,0,0,8,0,0,3,
    179   1,0,4,0,0,0,0,0,0,
    180   0,6,0,0,0,0,0,5,0,
    181   0,0,0,0,0,0,2,0,8,
    182   7,0,0,2,0,0,0,0,5,
    183   8,0,0,0,9,0,0,0,1,
    184   3,4,0,5,0,0,0,6,2
    185  };
    186  int data2[]=
    187  {7,4,0,0,8,0,0,1,6,
    188   9,0,0,0,3,5,0,0,4,
    189   0,0,0,7,0,0,0,0,0,
    190   0,7,0,0,0,9,5,0,0,
    191   6,1,0,0,5,0,0,8,7,
    192   0,0,2,6,0,0,0,4,0,
    193   0,0,0,0,0,4,0,0,0,
    194   3,0,0,5,6,0,0,0,2,
    195   5,6,0,0,1,0,0,3,9
    196  };
    197  int blanks;
    198  cout<<"随机生成一个数独,输入空格数";
    199  cin>>blanks;
    200  CSudoku s(blanks);
    201  s.display();
    202  cout<<"开始解数独:"<<endl;
    203  s.resolve();
    204  return 0;
    205 }
    206 
    207 测试运行结果:
    208 
    209 
    210 随机生成一个数独,输入空格数40
    211 [   ]  < 7 >  < 8 >  [   ]  [   ]  [   ]  [   ]  [   ]  < 6 >
    212 < 6 >  < 3 >  [   ]  < 7 >  [   ]  < 2 >  < 8 >  [   ]  < 5 >
    213 < 2 >  [   ]  [   ]  < 8 >  [   ]  [   ]  [   ]  [   ]  < 9 >
    214 < 3 >  < 9 >  [   ]  < 1 >  [   ]  [   ]  < 2 >  < 5 >  [   ]
    215 [   ]  [   ]  [   ]  < 2 >  < 5 >  < 4 >  < 6 >  [   ]  < 3 >
    216 < 4 >  [   ]  [   ]  [   ]  [   ]  [   ]  < 7 >  < 1 >  [   ]
    217 [   ]  [   ]  [   ]  < 4 >  < 7 >  [   ]  [   ]  < 3 >  < 1 >
    218 < 9 >  < 4 >  [   ]  < 6 >  < 2 >  < 1 >  < 5 >  < 8 >  [   ]
    219 [   ]  [   ]  < 7 >  < 9 >  < 3 >  [   ]  [   ]  < 6 >  < 2 >
    220 开始解数独:
    221 No. 1:
    222 < 1 >  < 7 >  < 8 >  < 5 >  < 4 >  < 9 >  < 3 >  < 2 >  < 6 >
    223 < 6 >  < 3 >  < 9 >  < 7 >  < 1 >  < 2 >  < 8 >  < 4 >  < 5 >
    224 < 2 >  < 5 >  < 4 >  < 8 >  < 6 >  < 3 >  < 1 >  < 7 >  < 9 >
    225 < 3 >  < 9 >  < 6 >  < 1 >  < 8 >  < 7 >  < 2 >  < 5 >  < 4 >
    226 < 7 >  < 8 >  < 1 >  < 2 >  < 5 >  < 4 >  < 6 >  < 9 >  < 3 >
    227 < 4 >  < 2 >  < 5 >  < 3 >  < 9 >  < 6 >  < 7 >  < 1 >  < 8 >
    228 < 5 >  < 6 >  < 2 >  < 4 >  < 7 >  < 8 >  < 9 >  < 3 >  < 1 >
    229 < 9 >  < 4 >  < 3 >  < 6 >  < 2 >  < 1 >  < 5 >  < 8 >  < 7 >
    230 < 8 >  < 1 >  < 7 >  < 9 >  < 3 >  < 5 >  < 4 >  < 6 >  < 2 >
    231 No. 2:
    232 < 1 >  < 7 >  < 8 >  < 5 >  < 4 >  < 9 >  < 3 >  < 2 >  < 6 >
    233 < 6 >  < 3 >  < 9 >  < 7 >  < 1 >  < 2 >  < 8 >  < 4 >  < 5 >
    234 < 2 >  < 5 >  < 4 >  < 8 >  < 6 >  < 3 >  < 1 >  < 7 >  < 9 >
    235 < 3 >  < 9 >  < 6 >  < 1 >  < 8 >  < 7 >  < 2 >  < 5 >  < 4 >
    236 < 7 >  < 8 >  < 1 >  < 2 >  < 5 >  < 4 >  < 6 >  < 9 >  < 3 >
    237 < 4 >  < 2 >  < 5 >  < 3 >  < 9 >  < 6 >  < 7 >  < 1 >  < 8 >
    238 < 8 >  < 6 >  < 2 >  < 4 >  < 7 >  < 5 >  < 9 >  < 3 >  < 1 >
    239 < 9 >  < 4 >  < 3 >  < 6 >  < 2 >  < 1 >  < 5 >  < 8 >  < 7 >
    240 < 5 >  < 1 >  < 7 >  < 9 >  < 3 >  < 8 >  < 4 >  < 6 >  < 2 >
    241 Press any key to continue

    rickone 2007/01/25

  • 相关阅读:
    “Win10 UAP 开发系列”之 在MVVM模式中控制ListView滚动位置
    “Win10 UAP 开发系列”之主题模式切换
    Windows Phone 8.1中AppBarToggleButton的绑定问题
    Windows Phone 8.1中处理后退键的HardwareButtons.BackPressed事件
    在后台代码中动态生成pivot项并设置EventTrigger和Action的绑定
    数据对象转json与md5加密注意事项
    iOS中wkwebview加载本地html的要点
    iOS项目开发常用功能静态库
    AFN中请求序列化的设置
    swift中的AnyHashable
  • 原文地址:https://www.cnblogs.com/gkfeng/p/2686452.html
Copyright © 2020-2023  润新知