@TOP
简单数独的DFS求解
1. 问题
给出9×9的标准数独,使用C语言编程完成这个数独的求解。
例如求解如下数独:
0 0 5 3 0 0 0 0 0
8 0 0 0 0 0 0 2 0
0 7 0 0 1 0 5 0 0
4 0 0 0 0 5 3 0 0
0 1 0 0 7 0 0 0 6
0 0 3 2 0 0 0 8 0
0 6 0 5 0 0 0 0 9
0 0 4 0 0 0 0 3 0
0 0 0 0 0 9 7 0 0
以上0表示数独中需要填入数组的空格
2.求解
2.1 数据结构设计
数独的定义决定了在C语言中使用一个二维数组就能完美的解决问题,所以这里使用9×9的二维数组存储数独,其中使用0表示需要填入数字的空格。考虑到程序的可扩展性,比如解决超级数独的情况,代码中使用#define N 9
这样的预定义表示数独大小。
2.2 算法设计
本案中可以暴力1-9从最左上角开始逐个尝试,但是关键问题是如何使用代码控制当发现尝试的数字不符合要求,回退到上一个数字的分析状态,然后尝试上一个数字的其他情况。其实这种情况是典型的DFS应用场景。
也就是说在每一个需要填写数字的位置,可以1-9逐个尝试 ,而每一个尝试又是可以递归成为对下一个位置1-9数字的尝试。
梳理一下算法如下:
- 判断是否已经将所有空格填满,如果填满输出数独然后直接退出程序。
- 判断当前需要处理的数独格子中,是否为0。
- 如果为0,表示当前格子是一个需要填入数字的空格,那么可以1-9尝试填入一个数字,并验证是否满足要求。
- 如果满足要求,那么暂时使用这个结果,并对下一个格子使用本算法递归分析。
- 如果所有1-9都不能满足,那么表明上一次填入的数字是错误的,所以要回溯到上一次分析状态中(这里实际上是可以通过递归函数的返回实现)。注意:由于前面的尝试过程中,已经在数独中尝试填写了各种数字,这时候回退上一层调用前,必须先恢复原来状态,也就是将单元格设置为0。
- 如果为1,表示当前单元格中原来就有数字,不需要我们处理,那么可以跳过这一个,处理下面一个格子。
- 如果为0,表示当前格子是一个需要填入数字的空格,那么可以1-9尝试填入一个数字,并验证是否满足要求。
2.3 主要代码
1.数独递归处理函数 sudoku_try
void sudoku_try(int s[N][N], int x, int y){
int i;
if ( x == N ){ /* 当前处理到N行了,也就是0~N-1都填完了,结束!*/
printf("\n");
print(s); /* 输出数独 */
exit(0); /* 退出程序 */
}
if ( s[x][y] == 0 ) {
for ( i=1; i <= N; i++ ) {
if ( test(s, x, y, i) ) {
s[x][y] = i;
sudoku_try(s, (y+1) / N + x, (y+1) % N);
}
}
s[x][y] = 0; /* 回退到上一层递归调用前,消除前面尝试遗留的数据。*/
}
else{
sudoku_try(s, (y+1) / N + x, (y+1) % N);
}
}
请注意以上代码中第5,15和18行代码含义,有助于理解。
2.检测函数 test
int test(int s[N][N], int x, int y, int k){
int i, j;
for (i = 0; i< N; i++) { /* 分别行列检测是否有k已经存在 */
if ( s[x][i] == k || s[i][y] == k ) return 0;
}
/* 检测各自所在小9宫格 */
for ( i = x / 3 * 3; i < x / 3 * 3 + 3; i++ )
for ( j = y / 3 * 3; j < y / 3 * 3 + 3; j++ )
if ( s[i][j] == k ) return 0;
return 1;
}
3.完整代码
#include <stdio.h>
#include <stdlib.h>
#define N 9
int test (int s[N][N], int x, int y, int k);
void print(int s[N][N]);
void sudoku_try(int s[N][N], int x, int y);
int main (int argc, char *argv[])
{
int sudoku[][N] = {
{0, 0, 5, 3, 0, 0, 0, 0, 0},
{8, 0, 0, 0, 0, 0, 0, 2, 0},
{0, 7, 0, 0, 1, 0, 5, 0, 0},
{4, 0, 0, 0, 0, 5, 3, 0, 0},
{0, 1, 0, 0, 7, 0, 0, 0, 6},
{0, 0, 3, 2, 0, 0, 0, 8, 0},
{0, 6, 0, 5, 0, 0, 0, 0, 9},
{0, 0, 4, 0, 0, 0, 0, 3, 0},
{0, 0, 0, 0, 0, 9, 7, 0, 0},
};
print(sudoku);
sudoku_try(sudoku, 0, 0);
return 0;
}
void sudoku_try(int s[N][N], int x, int y){
int i;
if ( x == N ){
printf("\n");
print(s); /* 求一个解后退出,如果需 */
exit(0); /* 要求出所有解,怎么修改?*/
}
if ( s[x][y] == 0 ) {
for ( i=1; i <= N; i++ ) {
if ( test(s, x, y, i) ) {
s[x][y] = i;
sudoku_try(s, (y+1) / N + x, (y+1) % N);
}
}
s[x][y] = 0; /* 请解释语句作用? */
}
else{
sudoku_try(s, (y+1) / N + x, (y+1) % N);
}
}
int test(int s[N][N], int x, int y, int k){
int i, j;
for (i = 0; i< N; i++) {
if ( s[x][i] == k || s[i][y] == k ) return 0;
}
for ( i = x / 3 * 3; i < x / 3 * 3 + 3; i++ )
for ( j = y / 3 * 3; j < y / 3 * 3 + 3; j++ )
if ( s[i][j] == k ) return 0;
return 1;
}
void print(int s[N][N]){
int i, j;
for ( i = 0; i < N; i++ ) {
for ( j = 0; j < N; j++)
printf("%3d", s[i][j]);
printf("\n");
}
}