zoukankan      html  css  js  c++  java
  • 数独(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

  • 相关阅读:
    服务器状态码
    QuerySet中添加Extra进行SQL查询
    django配置一个网站建设
    MySQL数据库查询中的特殊命令
    125. Valid Palindrome
    121. Best Time to Buy and Sell Stock
    117. Populating Next Right Pointers in Each Node II
    98. Validate Binary Search Tree
    91. Decode Ways
    90. Subsets II
  • 原文地址:https://www.cnblogs.com/gkfeng/p/2686452.html
Copyright © 2011-2022 走看看