数独游戏的解法到App的实现

在LeetCode上偶然刷到一个解数独的题目:
编写一个程序,通过已填充的空格来解决数独问题。

一个数独的解法需遵循如下规则:

  • 数字 1-9 在每一行只能出现一次。
  • 数字 1-9 在每一列只能出现一次。
  • 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
    空白格用 ‘.’ 表示。
    在这里插入图片描述
    一个数独。
    在这里插入图片描述
    答案被标成红色。

Note:

  • 给定的数独序列只包含数字 1-9 和字符 ‘.’ 。
  • 你可以假设给定的数独只有唯一解。
  • 给定数独永远是 9x9 形式的。

这个题目很是有趣,解决了数独算不出来的难题。
首先想到的是暴力枚举了,也就是对每个空格进行枚举,回溯法。

public void solveSudoku(char[][] board) {
		//boolean数组 表明是否被使用过,按照给定的规则
        boolean[][] rows = new boolean[9][10];//行
        boolean[][] cols = new boolean[9][10];//列
        boolean[][] boxes = new boolean[9][10];//每个3*3小块
		
		//先都初始化,有值的表示已被使用
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (board[i][j] != '.') {
                    int num = board[i][j] - '0';
                    rows[i][num] = true;
                    cols[j][num] = true;
                    boxes[(i / 3) * 3 + j / 3][num] = true;
                }
            }
        }
        //开始回溯
        bactTrack(board, rows, cols, boxes, 0, 0);

    }

    private boolean bactTrack(char[][] board, boolean[][] rows, boolean[][] cols, boolean[][] boxes, int i, int j) {
        //当回溯到达每行的边界时,进入下一行
        if (j == board[0].length) {
            j = 0;
            i++;
            //如果都遍历完成,也就是到达(9,9)处,找到其中一个解
            if (i == board.length) {
                return true;
            }
        }
        //如果是 空 格
        if (board[i][j] == '.') {
            for (int num = 1; num <= 9; num++) {
                int boxIndex = (i / 3) * 3 + j / 3;//这个时将i,j下标映射到对应的3*3小块中
                boolean isUesd = rows[i][num] || cols[j][num] || boxes[boxIndex][num];//判断是否被使用
                //如果没有被使用
                if (!isUesd) {
                    rows[i][num] = true;
                    cols[j][num] = true;
                    boxes[boxIndex][num] = true;
					
                    board[i][j] = (char) ('0' + num);
					//进入下一步
                    if (bactTrack(board, rows, cols, boxes, i, j + 1)) {
                        return true;
                    }
					//如果下一步无解,回溯
                    board[i][j] = '.';
                    rows[i][num] = false;
                    cols[j][num] = false;
                    boxes[boxIndex][num] = false;

                }
            }
        } else {//不是空格直接跳过
            return bactTrack(board, rows, cols, boxes, i, j + 1);
        }
		//前面都没找到解
        return false;
    }

以上的算法便是根据给出的数独得到数独的解。
然后突然想试试这个算法,但是没有生成数独的算法,找到了数独-- 一个高效率生成数独的算法博客里的方法,也验证了下,能达到随机生成的效果(虽然都是伪随机)

//seedArray是用来生成数独的种子数组
private void creatSudokuArray(int[][] seedArray) {
        //产生一个1-9的不重复长度为9的一维数组
        ArrayList<Integer> randomList = new ArrayList<>();
        Random random = new Random();
        for (int i = 0; i < 9; i++) {
            int randomNum = random.nextInt(9) + 1;
            while (true) {
                if (!randomList.contains(randomNum)) {
                    randomList.add(randomNum);
                    break;
                }
                randomNum = random.nextInt(9) + 1;
            }
        }

        /*
          通过一维数组和原数组生成随机的数独矩阵
          遍历二维数组里的数据,在一维数组找到当前值的位置,并把一维数组
          当前位置加一处位置的值赋到当前二维数组中。目的就是将一维数组为
          依据,按照随机产生的顺序,将这个9个数据进行循环交换,生成一个随
          机的数独矩阵。
         */

        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                for (int k = 0; k < 9; k++) {
                    if (seedArray[i][j] == randomList.get(k)) {
                        seedArray[i][j] = randomList.get((k + 1) % 9);
                        break;
                    }
                }
                //将生成的重新赋值给需要的,seedArray不做改变
                board[i][j] = seedArray[i][j];
            }
        }
    }

好了,有了生成数独的算法后,但是这只是个数独终盘,还需要挖空,最后利用随机数完成了不同等级的挖空

	/**
     * 给指定数独终盘挖空,利用随机数,随机次数为level,也就是对每行挖level次空
     * 难度对应easy--5, middle--7, hard--9
     * 每次挖空后判断是否有解,若无解重新挖空
     * @param level 挖空等级
     */
    public void resetSudoku(int level) {
        int[][] copies = new int[board.length][board[0].length];
        if (level < 5) {
            level = 5;
        }
        creatSudokuArray(seedArray);
        Random random = new Random();
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < level; j++) {
                int ran = random.nextInt(9);
                board[i][ran] = 0;
            }
        }
        if (!solveSudoku(copies)) {
            resetSudoku(level);
        }
    }

好了,有了这一系列的算法后,便可以自己写一个数独游戏了。
图片示意如下:

Github:DiyViewPracticeDemo

发布了38 篇原创文章 · 获赞 6 · 访问量 3388

猜你喜欢

转载自blog.csdn.net/qq_37704124/article/details/101105594