回溯法小结(leetcode37)解决数独问题

2019-01-14  本文已影响0人  小烈yhl

数独题如下,求出解答


数独

题目给定的数据是:
["5","3",".",".","7",".",".",".","."]
["6",".",".","1","9","5",".",".","."]
[".","9","8",".",".",".",".","6","."]
["8",".",".",".","6",".",".",".","3"]
["4",".",".","8",".","3",".",".","1"]
["7",".",".",".","2",".",".",".","6"]
[".","6",".",".",".",".","2","8","."]
[".",".",".","4","1","9",".",".","5"]
[".",".",".",".","8",".",".","7","9"]
‘.’代表空出的部分
题目给的方法是:

class Solution {
    public void solveSudoku(char[][] board) {   
    }

本题可以用回溯法来解决。

第一步
首先解决辅助部分的函数

  1. 查看放入的数据是否有效
  2. 用list记录空格的位置,这样我们就不用每次遍历整个表来填空格,而是直接定位空格,这样就可以用少量空间换出时间。
    实现
    1
    private boolean isValid(int row, int col, char c) {
        for (int i = 0; i < 9; i++) {
            if (board[i][col] != '.' && board[i][col] == c)
                return false; // 检查行
            if (board[row][i] != '.' && board[row][i] == c)
                return false; // 检查列
            if (board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] != '.'
                    && board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] == c)
                return false; // 检查3x3小方格
        }
        return true;
    }

2 此处注意num=9*i+j的模式,可以反编译为i = num/9j = num%9

记录空格的位置
    public void CountEmptyCell() {
        list = new ArrayList<>();
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++)
                if (board[i][j] == '.') {
                    list.add(9 * i + j);
                }
        }

    }

dfs函数用回溯法


递归树 image.png
    public boolean dfs(int cur) {
        if (cur == list.size()) {
            return true;
        }

        int decode = list.get(cur);
        int row = decode / 9;
        int col = decode % 9;
        for (char i = '1'; i <= '9'; i++) {
            if (isValid(row, col, i)) {
                board[row][col] = i;
                if (dfs(cur + 1))
                    return true;
                else
                    board[row][col] = '.';//一定要记得状态回滚
            }
        }
        return false;
    }

注意此处的dfs方法我用的是boolean返回类型,就是能够让其找到了答案后不会继续的递归,而是及时的中断返回(由于正解只有一个,所以我们不必继续遍历把所有的解都找出来,如果不这样做,那么在查找下一个解的时候,会把状态给返回到默认的没有填写数字的状态,如果之后一直没有找到解,那么最后的答案就会是初始状态,这是因为没有找到解,就会把状态还原)

以下是没考虑的错误写法


image.png

    public void dfs(int cur) { // 他也可以打印出结果,但是由于是全体枚举,即使找到了答案,还是会一直枚举之后的结果,看之后的结果是不是对的,但是这样的话就会冲掉我们的正确答案,因为只有一个解,其他的全是错误的!这样就会一直把我们填好的答案还原为'.'
        if (cur == list.size()) {
            for (int i = 0; i < 9; i++) {
                for (int j = 0; j < 9; j++) {
                    System.out.print(board[i][j] + " ");
                }
                System.out.println();
            }
            return;
        }

        int decode = list.get(cur);
        int row = decode / 9;
        int col = decode % 9;
        for (char i = '1'; i <= '9'; i++) {
            if (isValid(row, col, i)) {
                board[row][col] = i;
                dfs(cur + 1);
                board[row][col] = '.';
            }
        }

    }

这样,输出结果在输出第一个解后,继续遍历,然后后面又找不到解了,整个函数就会把结果,也就是我们的board[][]还原为初始状态。
[图片上传中...(image.png-c68388-1547468691246-0)]

错误的输出结果为

package com.leetcode;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Scanner;

import javax.print.attribute.IntegerSyntax;

public class leet37 {

    char[][] board;
    ArrayList<Integer> list;

    public char[][] solveSudoku(char[][] board) {
        this.board = board;
        CountEmptyCell();
        dfs(0);
        return board;

    }

/*  public boolean dfs(int cur) {
        if (cur == list.size()) {
            return true;

        }

        int decode = list.get(cur);
        int row = decode / 9;
        int col = decode % 9;
        for (char i = '1'; i <= '9'; i++) {
            if (isValid(row, col, i)) {
                board[row][col] = i;
                if (dfs(cur + 1))
                    return true;
                else
                    board[row][col] = '.';
            }
        }
        return false;

    }*/

    
      public void dfs(int cur) {//他也可以打印出结果,但是由于是全体枚举,即使找到了答案,还是会一直枚举之后的结果,看之后的结果是不是对的,但是这样的话就会冲掉我们的正确答案, 因为只有一个解,其他的全是错误的!这样就会一直把我们填好的答案还原为'.' 
          if (cur == list.size()) { 
              for (int i =0; i < 9; i++) { 
                  for (int j = 0; j < 9; j++) { 
                      System.out.print(board[i][j]+" ");
                     } 
                  System.out.println(); 
                  } 
              return;
      }
      
      int decode = list.get(cur); 
      int row = decode / 9; 
      int col = decode % 9; 
      for(char i = '1'; i <= '9'; i++) { 
          if (isValid(row, col, i)) { 
              board[row][col] =i; 
              dfs(cur + 1); 
              board[row][col] = '.'; 
              } 
          }
      
      }
     


    public void CountEmptyCell() {
        list = new ArrayList<>();
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++)
                if (board[i][j] == '.') {
                    list.add(9 * i + j);
                }
        }

    }

    private boolean isValid(int row, int col, char c) {
        for (int i = 0; i < 9; i++) {
            if (board[i][col] != '.' && board[i][col] == c)
                return false; // check row
            if (board[row][i] != '.' && board[row][i] == c)
                return false; // check column
            if (board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] != '.'
                    && board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] == c)
                return false; // check 3*3 block
        }
        return true;
    }

    public static void main(String[] args) throws FileNotFoundException {
        leet37 test = new leet37();
        File file = new File("C:\\Users\\yihon\\Desktop\\123.txt");
        Scanner sc = new Scanner(file);
        char[][] c = new char[9][9];
        int i = 0;
        while (sc.hasNextLine()) {
            String in = sc.nextLine();
            in = in.replace("[", "");
            in = in.replace("]", "");
            in = in.replaceAll("\"", "");
            String[] t = in.split(",");

            for (int j = 0; j < 9; j++)
                c[i][j] = t[j].charAt(0);

            i++;
        }
        for (i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                System.out.print(c[i][j] + " ");

            }
            System.out.println();
        }

        test.solveSudoku(c);

        for (i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                System.out.print(c[i][j] + " ");

            }
            System.out.println();
        }

    }

}

输入
5 3 . . 7 . . . . 
6 . . 1 9 5 . . . 
. 9 8 . . . . 6 . 
8 . . . 6 . . . 3 
4 . . 8 . 3 . . 1 
7 . . . 2 . . . 6 
. 6 . . . . 2 8 . 
. . . 4 1 9 . . 5 
. . . . 8 . . 7 9 
输出:
这次输出是由于我在dfs函数没cur==list.size()的情况下输出的结果,然后继续遍历
5 3 4 6 7 8 9 1 2 
6 7 2 1 9 5 3 4 8 
1 9 8 3 4 2 5 6 7 
8 5 9 7 6 1 4 2 3 
4 2 6 8 5 3 7 9 1 
7 1 3 9 2 4 8 5 6 
9 6 1 5 3 7 2 8 4 
2 8 7 4 1 9 6 3 5 
3 4 5 2 8 6 1 7 9 
main函数输出结果:(由于没有及时停止,继续遍历后没找到解,从而冲掉了原解)
5 3 . . 7 . . . . 
6 . . 1 9 5 . . . 
. 9 8 . . . . 6 . 
8 . . . 6 . . . 3 
4 . . 8 . 3 . . 1 
7 . . . 2 . . . 6 
. 6 . . . . 2 8 . 
. . . 4 1 9 . . 5 
. . . . 8 . . 7 9 

然后以下是正确解法的源代码:(这里我把给定函数的void,换成了char[][],主要是方便我本地输入测试用)

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Scanner;

import javax.print.attribute.IntegerSyntax;

public class leet37 {

    char[][] board;
    ArrayList<Integer> list;

    public char[][] solveSudoku(char[][] board) {
        this.board = board;
        CountEmptyCell();
        dfs(0);
        return board;

    }

    public boolean dfs(int cur) {
        if (cur == list.size()) {
            return true;

        }

        int decode = list.get(cur);
        int row = decode / 9;
        int col = decode % 9;
        for (char i = '1'; i <= '9'; i++) {
            if (isValid(row, col, i)) {
                board[row][col] = i;
                if (dfs(cur + 1))
                    return true;
                else
                    board[row][col] = '.';
            }
        }
        return false;

    }

    
    /*  public void dfs(int cur) {//他也可以打印出结果,但是由于是全体枚举,即使找到了答案,还是会一直枚举之后的结果,看之后的结果是不是对的,但是这样的话就会冲掉我们的正确答案, 因为只有一个解,其他的全是错误的!这样就会一直把我们填好的答案还原为'.' 
          if (cur == list.size()) { 
              for (int i =0; i < 9; i++) { 
                  for (int j = 0; j < 9; j++) { 
                      System.out.print(board[i][j]+" ");
                     } 
                  System.out.println(); 
                  } 
              return;
      }
      
      int decode = list.get(cur); 
      int row = decode / 9; 
      int col = decode % 9; 
      for(char i = '1'; i <= '9'; i++) { 
          if (isValid(row, col, i)) { 
              board[row][col] =i; 
              dfs(cur + 1); 
              board[row][col] = '.'; 
              } 
          }
      
      }*/
     


    public void CountEmptyCell() {
        list = new ArrayList<>();
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++)
                if (board[i][j] == '.') {
                    list.add(9 * i + j);
                }
        }

    }

    private boolean isValid(int row, int col, char c) {
        for (int i = 0; i < 9; i++) {
            if (board[i][col] != '.' && board[i][col] == c)
                return false; // check row
            if (board[row][i] != '.' && board[row][i] == c)
                return false; // check column
            if (board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] != '.'
                    && board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] == c)
                return false; // check 3*3 block
        }
        return true;
    }

    public static void main(String[] args) throws FileNotFoundException {
        leet37 test = new leet37();
        File file = new File("C:\\Users\\yihon\\Desktop\\123.txt");
        Scanner sc = new Scanner(file);
        char[][] c = new char[9][9];
        int i = 0;
        while (sc.hasNextLine()) {
            String in = sc.nextLine();
            in = in.replace("[", "");
            in = in.replace("]", "");
            in = in.replaceAll("\"", "");
            String[] t = in.split(",");

            for (int j = 0; j < 9; j++)
                c[i][j] = t[j].charAt(0);

            i++;
        }
        for (i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                System.out.print(c[i][j] + " ");

            }
            System.out.println();
        }

        test.solveSudoku(c);

        for (i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                System.out.print(c[i][j] + " ");

            }
            System.out.println();
        }

    }

}

运行结果

输入
["5","3",".",".","7",".",".",".","."]
["6",".",".","1","9","5",".",".","."]
[".","9","8",".",".",".",".","6","."]
["8",".",".",".","6",".",".",".","3"]
["4",".",".","8",".","3",".",".","1"]
["7",".",".",".","2",".",".",".","6"]
[".","6",".",".",".",".","2","8","."]
[".",".",".","4","1","9",".",".","5"]
[".",".",".",".","8",".",".","7","9"]
输出
5 3 4 6 7 8 9 1 2 
6 7 2 1 9 5 3 4 8 
1 9 8 3 4 2 5 6 7 
8 5 9 7 6 1 4 2 3 
4 2 6 8 5 3 7 9 1 
7 1 3 9 2 4 8 5 6 
9 6 1 5 3 7 2 8 4 
2 8 7 4 1 9 6 3 5 
3 4 5 2 8 6 1 7 9 

上一篇下一篇

猜你喜欢

热点阅读