回溯法简单应用--解数独

2022-10-21 07:56:46

简单介绍

数独是当下较为流行的数学游戏之一。通常数独由9x9的格子构成,其中将9x9的格子分为9个3x3的区域,称为“宫”(通常宫与宫之间会用较粗的线来分隔)。游戏的目标则是在格子中填满1~9的数字,并且使得数字之间满足一定的约束条件。

对于标准数独,其约数条件如下:

  • 在9x9的格子下,每行,每列不得有重复的数字
  • 每个宫内不得有重复的数字

除了标准数独,还有一系列的升级版本,例如连续数独,对角线数独,窗口数独,杀手数独等等。这里暂时只讨论标准数独的回溯解法。

对于同一个数独题目,存在着有多种解,也可能只有一种解。已知信息和解的数量通常也可以来衡量一个数独题目的难易程度,通常容易的数独给出的已知信息很多,又或者可能拥有很多个解。困难的数独则是给出的已知信息少,而最终解可能只有一个。

下面是关于最简单的破解数独的算法之一 —— 回溯法。回溯法采用深度优先搜索的思想,是常用的一种算法。关于回溯法的经典例子也很多,例如找生成树,0-1背包问题,8皇后问题,走迷宫,全排列问题等等。本文要说的解数独也可以采用回溯法。

本文采用的解数独思想就是最简单的一个个格子去试探,直到试探到把格子填满,既可以检查答案,如果符合则输出。根据这个思想和回溯法的一般步骤,可以得出解数独算法的步骤,此外,这里采用递归的形式。

回溯步骤一 问题的解空间

对于一个数独,每个格子都去试探实际上是一种暴力破解的思想,对于9x9的格子,每个格子可填1~9,如果不做任何限制,问题的解空间树将会是一棵九叉树,这样的搜索空间大小是非常可怕的。

回溯步骤二 确定搜索规则

在讨论解空间后,显然应该意识到,必须确定解空间树的拓展条件来抑制解空间树的指数增长。对于数独来说,搜索规则正好就是数独填数字的规则。对于一个待填格子,选是1~9,在1~9之中,同行同列同宫都出现过的数字就不必让其子节点生成了,从该节点开始,不符合数独规则的搜索结果最终必然不是数独的解。

回溯步骤三 剪枝

搜索规则实质上就是一个剪枝函数,通过剪枝函数提前剔除不可能的解,能够有效地控制解空间树的增长,并且随着搜索的推进,解空间树的增长会越来越缓慢。想象一下当你快要将数独填满的时候,剩下的格子的选择必然是越来越少,甚至能够直接确定某格子只有一个可行的数字。

伪代码描述

backTracking(intindex){if(index ==81){
        格子已经填满,输出结果
    }//x,y表示格子的坐标
    x <--index %9;
    y <--index /9;if(grid(x, y)的数字为题目的信息){
        backTracking(index+1);//题目给出的数字是确定的,没必要搜索
    }for(i=1; i<=9 ;i++){if(isLegal(x, y, i)){//逐个数字试探其合法性
            grid(x, y) = i;
            backTracking(index+1);
            grid(x, y) =0;
        }
    }
}

Java代码

import java.util.Scanner;publicclassSudoku {privatestaticint WIDTH =3;privatestaticint arr[][] =newint[WIDTH * WIDTH][WIDTH * WIDTH];privatestaticboolean q[][] =newboolean[WIDTH * WIDTH][WIDTH * WIDTH];publicstaticvoidmain(String[] args) {
        Scanner scanner =new Scanner(System.in);int val;for (int i =0; i <9; i++) {for (int j =0; j <9; j++) {
                val = scanner.nextInt();if (val !=0)
                    generateQuestion(i, j, val);
            }
        }for (int i =1; i < WIDTH * WIDTH; i++) {
            fill(0, i);
        }
    }privatestaticvoidfill(int index,int val) {int mWidth = WIDTH * WIDTH;if (index == mWidth * mWidth) {if (!nullCheck()) {
                printM();//                System.exit(0);
            }return;
        }int x = index % (mWidth);int y = index / (mWidth);if (q[y][x]) {
            fill(index +1, val);
        }else {if (isLegalPos(x, y, val)) {
                arr[y][x] = val;if (index +1 == mWidth * mWidth) {
                    fill(index +1,1);
                }elsefor (int i =1; i <= mWidth; i++) {
                        fill(index +1, i);
                    }
                arr[y][x] =0;
            }
        }
    }privatestaticbooleannullCheck() {for (int j =0; j < WIDTH; j++)for (int i =0; i < WIDTH; i++) {if (arr[j][i] ==0)returntrue;
            }returnfalse;
    }privatestaticbooleanisLegalPos(int x,int y,int val) {for (int i =0; i < WIDTH * WIDTH; i++) {if (x != i && arr[y][i] == val)returnfalse;if (y != i && arr[i][x] == val)returnfalse;
        }int offsetX = (x / WIDTH) * WIDTH;int offsetY = (y / WIDTH) * WIDTH;for (int i = offsetX; i < offsetX + WIDTH; i++)for (int j = offsetY; j < offsetY + WIDTH; j++) {if (!(y == j && x == i) && arr[j][i] == val)returnfalse;
            }returntrue;
    }privatestaticvoidprintM() {int k = WIDTH * WIDTH;for (int i =0; i < k; i++) {for (int j =0; j < k -1; j++) {
                System.out.printf("%d ", arr[i][j]);
            }
            System.out.println(arr[i][k-1]);
        }
    }privatestaticvoidgenerateQuestion(int y,int x,int val) {
        arr[y][x] = val;
        q[y][x] =true;
    }
}

样例测试(0表示待填)

输入1
8 0 0 0 0 0 0 0 0
0 0 3 6 0 0 0 0 0
0 7 0 0 9 0 2 0 0
0 5 0 0 0 7 0 0 0
0 0 0 0 4 5 7 0 0
0 0 0 1 0 0 0 3 0
0 0 1 0 0 0 0 6 8
0 0 8 5 0 0 0 1 0
0 9 0 0 0 0 4 0 0

输出1
8 1 2 7 5 3 6 4 9
9 4 3 6 8 2 1 7 5
6 7 5 4 9 1 2 8 3
1 5 4 2 3 7 8 9 6
3 6 9 8 4 5 7 2 1
2 8 7 1 6 9 5 3 4
5 2 1 9 7 4 3 6 8
4 3 8 5 2 6 9 1 7
7 9 6 3 1 8 4 5 2

输入2
0 0 3 0 1 9 0 0 0
9 0 0 8 0 0 0 0 0
0 1 0 6 2 0 0 0 9
0 8 0 0 0 0 0 4 1
5 2 6 0 0 0 9 8 7
4 9 0 0 0 0 0 6 0
6 0 0 0 8 3 0 9 0
0 0 0 0 0 2 0 0 3
0 0 0 4 6 0 8 0 0

输出2
2 6 3 5 1 9 4 7 8
9 7 5 8 3 4 1 2 6
8 1 4 6 2 7 5 3 9
3 8 7 9 5 6 2 4 1
5 2 6 3 4 1 9 8 7
4 9 1 2 7 8 3 6 5
6 5 2 1 8 3 7 9 4
1 4 8 7 9 2 6 5 3
7 3 9 4 6 5 8 1 2

  • 作者:Yotwei
  • 原文链接:https://blog.csdn.net/Kurozaki_Kun/article/details/78457228
    更新时间:2022-10-21 07:56:46