数独(sudoku),是一个填数字的游戏,规则简单,上到老爷爷老奶奶,下至小学生,都可以去解它,放松益脑。
一直以来就特别喜欢数独,第一次是从老爸手机上看到的,也做过不少题目。在初中的时候上发过了一本书,书的后面就有一个数独的题目,我是班上第一个也是唯一一个解出来的,十分骄傲。
最近学习了算法,发现里面的n皇后问题和数独特别的相似,感觉都可以使用回溯法在解空间树经行广度优先搜索。这种方法类似于穷举法,但并不是单纯的列举全部可能,不断的有剪枝函数减去不满足条件的子树。看到网上说一种叫做跳舞链的方法,下面的代码并不是采用这种方法,以后如果有时间可以去试一试。而我使用的是回溯法,使用语言是c语言。闲话就说到这里,下面分析一下代码。
核心内容:树的深度优先遍历
把数独想象成一个具有81层的树,然后在树上搜索剪枝。
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 //定义一个二维数组sudo[9][9]
5
6 /*
7 int sudo[9][9]={
8 { 5,7,6, -1,4,8, 1,3,-1},
9 { -1,3,-1, -1,-1,5, -1,8,-1},
10 { 8,2,-1, 3,-1,9, -1,-1,7},
11
12 { 1,-1,9, -1,-1,-1, -1,-1,8},
13 { -1,-1,3, 5,9,1, 6,-1,-1},
14 { 7,-1,-1, -1,-1,-1, 4,-1,1},
15
16 { 2,-1,-1, 1,-1,3, -1,4,5},
17 { -1,1,-1, 4,-1,-1, -1,2,-1},
18 { -1,4,7, 9,5,-1, 8,1,6,}
19 };
20 */
21 //测试数据1
22 int sudo[9][9]={
23 { 1,0,0, 0,5,0, 0,0,0},
24 { 4,0,2, 0,0,1, 0,3,0},
25 { 8,0,0, 0,0,6, 0,1,0},
26
27 { 0,0,0, 0,1,7, 6,0,0},
28 { 0,0,6, 5,0,2, 4,0,0},
29 { 0,0,7, 4,6,0, 0,0,0},
30
31 { 0,4,0, 9,0,0, 0,0,1},
32 { 0,9,0, 6,0,0, 7,0,5},
33 { 0,0,0, 0,3,0, 0,0,0}
34 };
35
36
37 int backtrack(int s[9][9],int row,int col);
38 //使用回溯法对解空间数经行深度搜索该函数就是深度搜索的函数
39
40 void showsudo();
41 //打印当前的数独中各个格子的值
42
43 int place(int s[9][9],int t,int j);
44 //剪枝函数
45
46 int justIsRepeatIN9(int s[9][9],int x,int y,int m,int n);
47 //剪枝函数中检查3*3小块合法的的函数
48
49
50 //从二维数组的左上角开始,向右搜索
51 //row表示行号,col表示列号
52 int backtrack(int s[9][9],int row,int col){
53
54 if(row == 9)
55 return 1;
56 //最后一个,在这之前是s[8][8];递归到最深处;成功返回
57
58 if(s[row][col] != 0){
59 if(col == 8){
60 row++;
61 col=0;
62 }
63 else
64 col++;
65 return backtrack(s,row,col);
66 }//如果 s[row][col]本来就有数据,需要跳过 ,分析下一个
67
68 // s[row][col]是空的,则放入1~9
69 for(int k=1;k<10;k++){
70 s[row][col]=k;
71 if(place(s,row,col)) {
72 //判断是否合法,如果合法就分析下一个
73 if(col == 8){
74 if(backtrack(s,row+1,0)){
75 return 1;
76 }
77 }
78 else{
79 if(backtrack(s,row,col+1)){
80 return 1;
81 }
82 }
83 }
84 s[row][col] = 0;
85 //在后面的某次中发现,所有的数都有冲突,则前面的数放错了,所以需要恢复
86 }
87 //从1~9没有找到合适的数放入,之前的出错了
88 return 0;
89 }
90
91 //判断整个数独是否合法
92 int place(int s[9][9],int t,int j)
93 {
94 for(int i=0;i<9;i++)
95 {
96 if(s[t][i] == s[t][j] && i!=j){
97 return 0 ;
98 }//判断行上没有冲突
99 if(s[i][j] == s[t][j] && i!=t){
100 return 0 ;
101 }//判断列上没有冲突
102 }
103
104 //判断块中是否有重复的
105 int x=t%3;
106 int y=j%3;
107 /*
108 00 01 02
109 10 11 12
110 20 21 22
111 */
112 if(x == 0 && y == 0){
113 if(justIsRepeatIN9(s,t,j,t,j)){
114 return 1;
115 }
116 else{
117 return 0;
118 }
119 }
120
121 if(x == 0 && y == 1){
122 if(justIsRepeatIN9(s,t,j-1,t,j)){
123 return 1;
124 }
125 else{
126 return 0;
127 }
128 }
129
130 if(x == 0 && y == 2){
131 if(justIsRepeatIN9(s,t,j-2,t,j)){
132 return 1;
133 }
134 else{
135 return 0;
136 }
137 }
138
139 if(x == 1 && y == 0){
140 if(justIsRepeatIN9(s,t-1,j,t,j)){
141 return 1;
142 }
143 else{
144 return 0;
145 }
146 }
147
148 if(x == 1 && y == 1){
149 if(justIsRepeatIN9(s,t-1,j-1,t,j)){
150 return 1;
151 }
152 else{
153 return 0;
154 }
155 }
156
157 if(x == 1 && y == 2){
158 if(justIsRepeatIN9(s,t-1,j-2,t,j)){
159 return 1;
160 }
161 else{
162 return 0;
163 }
164 }
165
166 if(x == 2 && y == 0){
167 if(justIsRepeatIN9(s,t-2,j,t,j)){
168 return 1;
169 }
170 else{
171 return 0;
172 }
173 }
174
175 if(x == 2 && y == 1){
176 if(justIsRepeatIN9(s,t-2,j-1,t,j)){
177 return 1;
178 }
179 else{
180 return 0;
181 }
182 }
183
184 if(x == 2 && y == 2){
185 if(justIsRepeatIN9(s,t-2,j-2,t,j)){
186 return 1;
187 }
188 else{
189 return 0;
190 }
191 }
192 }
193
194 int justIsRepeatIN9(int s[9][9],int x,int y,int m,int n){
195 //判断一个九宫格中是否有重复的数
196 //其中s是数组,x是起始的位置,y是起始是位置
197 //因为之前的数据都是有序的,并不需要判断其他的是否合法,只要判断指定的数
198 //只要知道特殊的
199 //m,n是需要判断的内容
200 for(int i=0;i<3;i++){
201 for(int j=0;j<3;j++){
202 if(s[x+i][y+j] == s[m][n] && x+i != m && y+j != n){
203 return 0;
204 }
205 }
206 }
207 return 1;
208 }
209
210 //打印数独中各个格子的值
211 void showsudo()
212 {
213 for(int i=0;i<9;i++){
214 for(int j=0;j<9;j++)
215 {
216 printf("%2d ",sudo[i][j]) ;
217 if(j%3 == 2)
218 {
219 printf(" ");
220 }
221 }
222 printf("\n");
223 if(i%3 == 2){
224 printf("\n");
225 }
226 }
227 printf("\n");
228 }
229
230 int main()
231 {
232 showsudo();
233 backtrack(sudo,0,0) ;
234 showsudo();
235 return 0;
236 }