數獨遊戲的解法到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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章