數獨(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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM