使用java解数独

说明

  • 先根据规则解数独,
    • 规则1: 如果备选数字只有一个, 那么就填入这个数字
    • 规则2: 如果在3*3单元格中, 或者一行, 或者一列中, 某个备选数字在所有的备选数字中只出现了一次, 那么就填入这个数字.
  • 再暴力破解数独, 依次填入备选数字, 如果不能解开, 换下一个备选数字, 直到数独完全解开.

代码

package com.example.springboot01;

import org.junit.Test;
import org.junit.platform.commons.util.StringUtils;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class Test0805 {

    /**
     * 解数独
     * @param board 待解数独
     */
    public void solveSudoku(char[][] board) {
        // 存储备选数字,全部都是null时,表示数独完全解析出来了
        String[][] newBoard = new String[9][9];

        // 统计备选数字
        solveSudoku1(board, newBoard);

        // 使用规则1和规则2解数独
        boolean isContinue = true;
        while (isContinue) {
            isContinue = solveSudoku2(board, newBoard) || solveSoduko3(board, newBoard);
        }

        // 暴力破解数独, 破解速度和备选数字的先后排序有很大关联
        int result = checkSodukoState(newBoard);
        if (2 == result) {
            solveSudoku4(0, 0, board, newBoard);
        }

        printStringArr(newBoard);
    }

    /**
     * 统计备选数字
     * @param board 待解数独
     * @param newBoard 剩余备选数字
     */
    public void solveSudoku1(char[][] board, String[][] newBoard) {
        // 初始化备选数字
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if ('.' == board[i][j]) {
                    newBoard[i][j] = "123456789";
                }
            }
        }

        // 从备选数字中排除掉确定数组
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if ('.' != board[i][j]) {
                    cleaNum(i, j, board[i][j]+"", newBoard);
                }
            }
        }
    }


    /**
     * 使用待选数字排除法,直到待选数字只剩一个
     * @param board 待解数独
     * @param newBoard 剩余备选数字
     */
    public boolean solveSudoku2(char[][] board, String[][] newBoard) {
        boolean isContinue = false;

        // 使用待选数字排除法,直到待选数字只剩一个
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if ('.' == board[i][j] && newBoard[i][j].length() == 1) {
                    char item = newBoard[i][j].charAt(0);
                    board[i][j] = item;
                    newBoard[i][j] = null;
                    isContinue = true;

                    // 清除备选数字
                    cleaNum(i, j, item + "", newBoard);
                }
            }
        }
        return isContinue;
    }

    /**
     * 清除行,列,3*3单元格内的指定备选数字
     * @param i 行
     * @param j 列
     * @param item 待清除数字
     * @param newBoard 备选数字
     */
    private void cleaNum(int i, int j, String item, String[][] newBoard) {
        // 清除备选数字
        // 3*3单元格起始坐标(左上角)
        int blockRow = (i / 3) * 3;
        int blockCol = (j / 3) * 3;
        for (int m = 0; m < 9; m++) {
            // 清除掉行里面的备选数字item
            if (newBoard[i][m] != null) {
                newBoard[i][m] = newBoard[i][m].replace(item, "");
            }

            // 清除掉列里面的备选数字item
            if (newBoard[m][j] != null) {
                newBoard[m][j] = newBoard[m][j].replace(item, "");
            }

            // 清除掉3*3单元格里面的备选数字item
            int x = blockRow + (m/3);
            int y = blockCol + (m%3);
            if (newBoard[x][y] != null) {
                newBoard[x][y] = newBoard[x][y].replace(item, "");
            }
        }
    }

    /**
     * 一个3*3单元格,一行或者一列的所有备选数字中,如果某个数只出现了一次,那么这个数就是最终数字
     * @param board 待解数独
     * @param newBoard 剩余备选数字
     * @return 是否成功解开一个数字
     */
    private boolean solveSoduko3(char[][] board, String[][] newBoard) {
        boolean isContinue = false;
        // 行
        for (int i = 0; i < 9; i++) {
            Map<Character, Integer> numToRegion = new HashMap<>();
            Map<Character, Integer> map = new HashMap<>();

            for (int j = 0; j < 9; j++) {
                if (newBoard[i][j] != null) {
                    char[] chars = newBoard[i][j].toCharArray();
                    for (char item : chars) {
                        numToRegion.put(item, j);
                        if (map.containsKey(item)) {
                            map.put(item, map.get(item) + 1);
                        } else {
                            map.put(item, 1);
                        }
                    }
                }
            }

            for (char item : map.keySet()) {
                if (map.get(item) == 1) {
                    isContinue = true;
                    int j = numToRegion.get(item);
                    board[i][j] = item;
                    newBoard[i][j] = null;

                    // 清除备选数字
                    cleaNum(i, j, item+"", newBoard);
                }
            }
        }

        // 列
        for (int j = 0; j < 9; j++) {
            Map<Character, Integer> numToRegion = new HashMap<>();
            Map<Character, Integer> map = new HashMap<>();

            for (int i = 0; i < 9; i++) {
                if (newBoard[i][j] != null) {
                    char[] chars = newBoard[i][j].toCharArray();
                    for (char item : chars) {
                        numToRegion.put(item, i);
                        if (map.containsKey(item)) {
                            map.put(item, map.get(item) + 1);
                        } else {
                            map.put(item, 1);
                        }
                    }
                }
            }

            for (char item : map.keySet()) {
                if (map.get(item) == 1) {
                    isContinue = true;
                    int i = numToRegion.get(item);
                    board[i][j] = item;
                    newBoard[i][j] = null;

                    // 清除备选数字
                    cleaNum(i, j, item+"", newBoard);
                }
            }
        }

        // 3*3单元格,一共9个
        for (int x = 0; x < 9; x++) {
            int row = (x/3)*3;
            int col = (x%3)*3;

            // 存储每个备选数字对应的坐标
            Map<Character, String> numToRegion = new HashMap<>();
            // 存储备选数字的个数
            Map<Character, Integer> map = new HashMap<>();
            for (int i = row; i < row+3; i++) {
                for (int j = col; j < col+3; j++) {
                    if (newBoard[i][j] != null) {
                        char[] chars = newBoard[i][j].toCharArray();
                        for (char item : chars) {
                            numToRegion.put(item, i + "," + j);
                            if (map.containsKey(item)) {
                                map.put(item, map.get(item) + 1);
                            } else {
                                map.put(item, 1);
                            }
                        }
                    }
                }
            }

            for (char item : map.keySet()) {
                // 如果备选个数为1,那么就填入这个数字
                if (map.get(item) == 1) {
                    String[] location = numToRegion.get(item).split(",");
                    int i = Integer.parseInt(location[0]);
                    int j = Integer.parseInt(location[1]);
                    board[i][j] = item;
                    newBoard[i][j] = null;
                    isContinue = true;

                    // 清除备选数字
                    cleaNum(i, j, item+"", newBoard);
                }
            }
        }
        return isContinue;
    }

    /**
     * 暴力拆除法,依次尝试备选数字
     * @param board1 待解数独
     * @param newBoard1 剩余备选数字
     * @return 处理结果,返回 1 表示解数独完成,返回 2 表示没有解, 返回 3 表示备份数字错误,需要继续尝试下一个
     */
    private int solveSudoku4(int startRow, int startCol, char[][] board1, String[][] newBoard1) {
        // 复制
        char[][] board = new char[9][9];
        String[][] newBoard = new String[9][9];
        for (int m = 0; m < 9; m++) {
            for (int n = 0; n < 9; n++) {
                board[m][n] = board1[m][n];
                newBoard[m][n] = newBoard1[m][n];
            }
        }

        // 随机选择一个备选数字
        for (int i = startRow; i < 9; i++) {
            for (int j = startCol; j < 9; j++) {
                if (StringUtils.isNotBlank(newBoard[i][j])) {
                    int length = newBoard[i][j].length();
                    for (int k = 0; k < length; k++) {
                        char item = newBoard[i][j].charAt(k);
                        board[i][j] = item;
                        newBoard[i][j] = null;

                        cleaNum(i, j, item+"", newBoard);

                        boolean isContinue2 = true;
                        while (isContinue2) {
                            isContinue2 = solveSudoku2(board, newBoard) || solveSoduko3(board, newBoard);
                        }

                        int result = checkSodukoState(newBoard);

                        if (1 == result) {
                            // 复制最终结果
                            for (int m = 0; m < 9; m++) {
                                for (int n = 0; n < 9; n++) {
                                    board1[m][n] = board[m][n];
                                    newBoard1[m][n] = newBoard[m][n];
                                }
                            }
                            return 1;
                        } else if (2 == result) {
                            int result2 = solveSudoku4(i, j, board, newBoard);
                            if (1 == result2) {
                                for (int m = 0; m < 9; m++) {
                                    for (int n = 0; n < 9; n++) {
                                        board1[m][n] = board[m][n];
                                        newBoard1[m][n] = newBoard[m][n];
                                    }
                                }
                                return 1;
                            }
                        }

                        // 当最后一个备份数字也不符合时,需要修改前面一位备份数字
                        if (k == length-1 && result == 3) {
                            return 3;
                        }

                        // 回退到上一步,尝试下一个字符
                        for (int m = 0; m < 9; m++) {
                            for (int n = 0; n < 9; n++) {
                                board[m][n] = board1[m][n];
                                newBoard[m][n] = newBoard1[m][n];
                            }
                        }
                    }
                }
            }
        }

        return 3;
    }

    /**
     * 判断数独状态
     * 1.newBoard值全为null, 表示全部解开
     * 2.newBoard里面有数字, 表示未完全解开
     * 3.newBoard里面没有数字,存在空字符串,表示数独不存在唯一解
     * @param newBoard 备选数字
     * @return 数独状态
     */
    private int checkSodukoState(String[][] newBoard) {
        boolean isAllNull = true;

        for (int i=0; i<9; i++) {
            for (int j=0; j<9; j++) {
                if (newBoard[i][j] != null) {
                    isAllNull = false;

                    if ("".equals(newBoard[i][j])) {
                        return 3;
                    }
                }
            }
        }

        return isAllNull ? 1 : 2;
    }

    private final String[] ten = new String[]{"1","2","3","4","5","6","7","8","9"};

    private String getRestNum(String str) {
        String retStr = "";
        for (String item : ten) {
            if (!str.contains(item)) {
                retStr += item;
            }
        }
        return retStr;
    }

    private void printArr(char[][] chars) {
        System.out.println("===============================================================================");
        for (int i=0; i<9; i++) {
            for (int j=0; j<9; j++) {
                System.out.print(chars[i][j] + "\t");
            }
            System.out.println();
        }
        System.out.println("===============================================================================");
    }

    private void printStringArr(String[][] strs) {
        System.out.println("===============================================================================");
        for (int i=0; i<9; i++) {
            for (int j=0; j<9; j++) {
                System.out.print(strs[i][j] + "\t");
            }
            System.out.println();
        }
        System.out.println("===============================================================================");
    }



    @Test
    public void testLikou() {
        Set<Integer> temp = new HashSet<>();
        temp.add(1);
        temp.add(2);
        temp.remove(1);
        //temp.remove(2);


        long startTime = System.currentTimeMillis();
        int n=8;
        int k=8;

        if (k > n) {
            return;
        }

        if (k == 0) {
            return;
        }

        //combine4(n, k);
        //System.out.println(allCombineList.size());

        /*char[][] chars = new char[][]{
                {'.','.','9','7','4','8','.','.','.'},
                {'7','.','.','.','.','.','.','.','.'},
                {'.','2','.','1','.','9','.','.','.'},
                {'.','.','7','.','.','.','2','4','.'},
                {'.','6','4','.','1','.','5','9','.'},
                {'.','9','8','.','.','.','3','.','.'},
                {'.','.','.','8','.','3','.','2','.'},
                {'.','.','.','.','.','.','.','.','6'},
                {'.','.','.','2','7','5','9','.','.'}};*/

        /*char[][] chars = new char[][]{
                {'5','.','2','.','6','.','.','.','.'},
                {'.','.','.','4','.','.','.','8','2'},
                {'.','.','.','.','.','2','.','.','1'},
                {'.','.','.','8','.','.','.','5','.'},
                {'.','.','3','.','.','.','6','.','.'},
                {'.','7','.','.','.','9','.','.','.'},
                {'8','.','.','2','.','.','.','.','.'},
                {'2','4','.','.','.','1','.','.','.'},
                {'.','.','.','.','3','.','2','.','7'}};*/

        /*char[][] chars = new char[][]{
                {'.','.','8','.','.','.','2','.','.'},
                {'.','3','.','8','.','2','.','6','.'},
                {'7','.','.','.','9','.','.','.','5'},
                {'.','5','.','.','.','.','.','1','.'},
                {'.','.','4','.','.','.','6','.','.'},
                {'.','2','.','.','.','.','.','7','.'},
                {'4','.','.','.','8','.','.','.','6'},
                {'.','7','.','1','.','3','.','9','.'},
                {'.','.','1','.','.','.','8','.','.'}};*/

        // 专家级
        /*char[][] chars = new char[][]{
                {'8','.','6','.','5','.','3','.','.'},
                {'.','5','.','.','.','4','.','.','2'},
                {'4','.','.','.','.','.','.','1','.'},
                {'.','.','.','3','.','.','2','.','9'},
                {'7','.','5','.','.','.','.','.','.'},
                {'.','.','.','.','8','1','.','.','.'},
                {'.','.','8','2','.','.','9','.','.'},
                {'.','.','.','4','.','.','.','.','8'},
                {'.','2','.','.','6','3','.','.','.'},};*/

        /*char[][] chars = new char[][]{
                {'6','.','.','.','.','.','5','.','.'},
                {'.','1','.','.','.','2','.','.','4'},
                {'.','.','7','.','3','.','.','6','.'},
                {'.','.','.','.','.','4','.','2','.'},
                {'.','.','.','.','6','1','.','.','.'},
                {'9','3','.','.','.','.','4','8','.'},
                {'.','8','.','7','1','.','.','9','.'},
                {'.','2','.','.','.','.','.','.','7'},
                {'7','.','.','.','5','.','.','.','.'},};*/

        // 世上最难数独
        char[][] chars = new char[][]{
                {'8','.','.','.','.','.','.','.','.'},
                {'.','.','3','6','.','.','.','.','.'},
                {'.','7','.','.','9','.','2','.','.'},
                {'.','5','.','.','.','7','.','.','.'},
                {'.','.','.','.','4','5','7','.','.'},
                {'.','.','.','1','.','.','.','3','.'},
                {'.','.','1','.','.','.','.','6','8'},
                {'.','.','8','5','.','.','.','1','.'},
                {'.','9','.','.','.','.','4','.','.'}};

        /*char[][] chars = new char[][]{
                {'.','.','.','.','5','.','.','3','.'},
                {'.','.','.','.','4','.','.','.','.'},
                {'.','9','.','.','.','.','.','.','2'},
                {'.','.','.','.','.','.','.','.','.'},
                {'6','.','.','.','.','.','.','4','.'},
                {'.','.','.','8','.','2','.','.','7'},
                {'5','.','.','.','.','.','.','.','.'},
                {'3','.','4','.','6','.','.','.','.'},
                {'.','.','.','7','.','.','2','.','9'}};*/

        solveSudoku(chars);
        printArr(chars);

        System.out.println("耗时: " + (System.currentTimeMillis() - startTime));


    }

}


结果

使用java解数独_第1张图片

  • 这边用了递归, 试了几个数独, 都能在1秒之内解开, 相比较而言, 数独只有九宫格, 所以暴力算法不算太耗时间.
  • 数独只知道这两个规则, 其他的没弄明白, 看百度百科上面介绍了一堆, 应该还能更快一点.
  • 暴力破解的速度很大程度上受到备选数字排序的影响.

参考

力扣解数独

你可能感兴趣的:(土味,java,算法,递归算法)