深度优先搜索之数独游戏

1. 问题描述:

你一定听说过“数独”游戏。
如下图所示,玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个同色九宫内的数字均含1-9,不重复。
数独的答案都是唯一的,所以,多个解也称为无解。
本图的数字据说是芬兰数学家花了3个月的时间设计出来的较难的题目。但对会使用计算机编程的你来说,恐怕易如反掌了。
本题的要求就是输入数独题目,程序输出数独的唯一解。我们保证所有已知数据的格式都是合法的,并且题目有唯一的解。
格式要求,输入9行,每行9个数字,0代表未知,其它数字为已知。
输出9行,每行9个数字表示数独的解。
输入:

005300000
800000020
070010500
400005300
010070006
003200080
060500009
004000030
000009700

程序应该输出:
145327698
839654127
672918543
496185372
218473956
753296481
367542819
984761235
521839764

再例如,输入:
800000000
003600000
070090200
050007000
000045700
000100030
001000068
008500010
090000400

程序应该输出: 
812753649
943682175
675491283
154237896
369845721
287169534
521974368
438526917
796318452

数独游戏使用深度优先搜索的方法是比较方便的,可以搜出数独的所有合法的解(使用其他的方法来解决是非常困难的或者是不可能的)

深度优先搜索是自上而下进行搜索,直到搜索出第一个合法的解返回,然后退到上一层搜索它的兄弟,对它的兄弟然后继续递归搜索下去,在循环中尝试着每一条路径,假如某一条路不能够走下去那么它也会退到上一层然后在循环中尝试着下一个解,即走下一条可能走得通的路径然后继续递归走下去,所以说深度优先搜索能够搜索出所有的可能,而这种解决方法是其他迭代的方法不能够解决的

深度优先搜索:先纵后横,先走一条能够到达终点的路径然后遇到递归的出口那么退回到上一层循环调用它的地方搜它的兄弟,每一层递归完成之后都会往上一层退回去然后继续递归下去

深度优先搜索通常是循环中嵌套递归,搜索完所有的路径后最终退出递归,其中可能涉及到回溯的问题

以数独游戏为例,当循环到某一个二维数组的某一个位置的时候那么假如填的数字合法那么对下一个空的数组的位置进行填写数字,但是如果经过循环之后发现没有一个数字可以填入这个空的位置那么循环结束之后递归退到上一层,在上一层中寻找其他的结果,假如其他的解合法那么会继续进行下一次的递归走下一条路,但是有可能在上一层中的其他数字除了之前填入的那么数字合法其他都不合法,那么填入的上一层的这个数字是不合法的,上一层的循环结束,此时就需要使用到回溯把之前填的这个不合法的数字清掉,重置为零,重新退到再上一层尝试再上一层的其他解然后继续走下去

每个位置上都可能存在着1-9这个范围的某些解,那么经过筛选之后可能只剩下一些解,当尝试到某个解合法的时候继续填下一个位置那么

那么当其他的层递归完成之后退到这一层的时候通过循环尝试着其他的可能的合法解,直到搜索完全部的解就结束了

这里使用到回溯是为了清除掉某个位置中不合法的解,然后退到上一层再调用下一层的时候数组才不会乱套

3.深度优先搜索经典的代码都是循环中嵌套递归的方法来进行实现的,然后在递归的时候加上递归的出口,因为这道题目只有唯一的解,那么假如求解到第一个解之后我们可以直接退出系统,而不是使用return,因为使用return的话会退到上一层搜索它的兄弟,而这道题是不需要这样的,其他的题目可能需要搜出所有解那么就要使用return,具体的代码如下:

import java.util.Scanner;
public class Main{
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        char arr[][] = new char[9][];
        for(int i = 0; i < 9; i++) {
             arr[i] = sc.nextLine().toCharArray();
        }
        dfs(arr, 0, 0);
    }

    //深度优先搜索
    private static void dfs(char[][] arr, int x, int y) {
        if(x == 9){
            print(arr);
            //因为这道题目限制了只有唯一的解那么不求出一个解之后直接退出不要使用return
            //入股哦使用return的话会搜索它的兄弟
            System.exit(0);
        }
        if(arr[x][y] == '0'){
            for(int i = 1; i < 10; i++){
                if(check(arr, x, y, i)){
                    arr[x][y] = (char)('0' + i);
                    dfs(arr, x + (y + 1) / 9, (y + 1) % 9);
                }
            }
            //回溯: 因为有可能不满足条件返回这一层的时候只有这个数字满足条件那么这个数字循环下去都没有数字满足条件
            //那么需要把这个数字重新恢复为零返回上一层继续递归尝试再上一层的其他解
            //填空的时候每个空都有1-9个数字进行选择然后进行筛选看一下哪一个符合条件 等到它下面的兄弟都完了之后再尝

            //试这一层的其他合法解
            arr[x][y] = '0';   
        }else{
            //System.out.println("zhang");
            dfs(arr, x + (y + 1) / 9, (y + 1) % 9);
        }    
    }

    private static boolean check(char[][] arr, int x, int y, int k){
        for(int i = 0; i < 9; i++){
            if(arr[x][i] == (char)('0' + k) || arr[i][y] == (char)('0' + k))
                return false;    
        }
        //检查九宫格
        for(int i = (x / 3) * 3; i < (x / 3 + 1) * 3; i++){
            for(int j = (y / 3) * 3; j < (y / 3 + 1) * 3; j++){
                if(arr[i][j] == (char)('0' + k)){
                    return false;
                }
            }
        }
        return true;
    }

    private static void print(char[][] arr) {
        for(int i = 0; i < 9; i++){
            for(int j = 0; j < 9; j++){
                System.out.print(arr[i][j]);
            }
            System.out.print("\n");
        }
    }
}
 

猜你喜欢

转载自blog.csdn.net/qq_39445165/article/details/83245096