0
点赞
收藏
分享

微信扫一扫

简单数独的DFS求解

RockYoungTalk 2022-01-24 阅读 57

@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数字的尝试。

梳理一下算法如下:

  1. 判断是否已经将所有空格填满,如果填满输出数独然后直接退出程序。
  2. 判断当前需要处理的数独格子中,是否为0。
    • 如果为0,表示当前格子是一个需要填入数字的空格,那么可以1-9尝试填入一个数字,并验证是否满足要求。
      • 如果满足要求,那么暂时使用这个结果,并对下一个格子使用本算法递归分析。
      • 如果所有1-9都不能满足,那么表明上一次填入的数字是错误的,所以要回溯到上一次分析状态中(这里实际上是可以通过递归函数的返回实现)。注意:由于前面的尝试过程中,已经在数独中尝试填写了各种数字,这时候回退上一层调用前,必须先恢复原来状态,也就是将单元格设置为0。
    • 如果为1,表示当前单元格中原来就有数字,不需要我们处理,那么可以跳过这一个,处理下面一个格子。

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");
    }
}
举报

相关推荐

0 条评论