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