Algorithm 如何生成具有独特解决方案的数独板

Algorithm 如何生成具有独特解决方案的数独板,algorithm,puzzle,sudoku,Algorithm,Puzzle,Sudoku,如何生成具有独特解决方案的数独板?我的想法是初始化一个随机板,然后删除一些数字。但我的问题是如何保持解决方案的唯一性?要给出一个通用的解决方案并不容易。你需要知道一些事情来生成一个特定的数独游戏。。。例如,您不能构建包含九个以上空9号组(行、3x3块或列)的数独。单解数独中的最小给定数字(即“线索”)被认为是17,但如果我没有错的话,这个数独的数字位置是非常具体的。一个数独的平均线索数大约是26,我不确定,但是如果你放弃一个完整的网格,直到有26个,然后以对称的方式保留这些,你可能拥有一个有效的

如何生成具有独特解决方案的数独板?我的想法是初始化一个随机板,然后删除一些数字。但我的问题是如何保持解决方案的唯一性?

要给出一个通用的解决方案并不容易。你需要知道一些事情来生成一个特定的数独游戏。。。例如,您不能构建包含九个以上空9号组(行、3x3块或列)的数独。单解数独中的最小给定数字(即“线索”)被认为是17,但如果我没有错的话,这个数独的数字位置是非常具体的。一个数独的平均线索数大约是26,我不确定,但是如果你放弃一个完整的网格,直到有26个,然后以对称的方式保留这些,你可能拥有一个有效的数独。
另一方面,您可以从已完成的网格中随机退出数字,并使用检查器或其他工具进行测试,直到得到“确定”为止。

您可以作弊。从一个可以解决的现有数独板开始,然后摆弄它

您可以将三个3x3块的任意行与任何其他行交换。可以将三个3x3块的任何列与另一列交换。在每个块行或块列中,可以交换单行和单列。最后,您可以排列数字,以便在填充的位置中有不同的数字,只要排列在整个板上是一致的

所有这些更改都不会使可解决的电路板无法解决。

简单:

  • 使用有效的回溯算法查找所有解决方案
  • 如果只有一个解决方案,那么就完成了。否则,如果您有多个解决方案,请找到大多数解决方案不同的位置。在这个位置加上数字
  • 转到1

  • 我怀疑你能找到比这快得多的解决方案。

    我自己的数独程序就是这样做的:


  • 从一个完整、有效的电路板开始(填充81个数字)

  • 列出所有81个单元格位置,然后随机洗牌

  • 只要列表不是空的,就从列表中选择下一个位置,并从相关单元格中删除该号码

  • 使用快速回溯解算器测试唯一性。理论上,我的解算器能够计算所有解,但为了测试唯一性,当它找到多个解时,它将立即停止

  • 如果当前电路板仍然只有一个解决方案,请转到步骤3)并重复

  • 如果当前电路板有多个解决方案,请撤消最后一次拆卸(步骤3),然后从列表中的下一个位置继续步骤3

  • 测试完所有81个位置后停止


  • 这不仅为您提供了唯一的电路板,而且还提供了在不破坏解决方案唯一性的情况下无法删除更多数字的电路板

    当然,这只是算法的后半部分。前半部分是先找到一个完整的有效板(随机填充!),其工作原理非常相似,但“方向相反”:


  • 从一块空木板开始

  • 在其中一个空闲单元格中添加一个随机数(该单元格是随机选择的,根据数独规则,该数字是从对该单元格有效的数字列表中随机选择的)

  • 使用回溯解算器检查当前电路板是否至少有一个有效的解决方案。如果没有,请撤消步骤2并使用另一个数字和单元格重复。请注意,此步骤可能会自行生成完全有效的电路板,但这些电路板绝不是随机的

  • 重复此操作,直到电路板上完全充满数字


  • 除非p=NP,否则没有多项式时间算法来生成只有一个解的一般数独问题

    在他的硕士论文中,Takayuki Yato定义了(ASP),其中的目标是,给定一个问题和一些解决方案,找到一个不同的解决方案来解决这个问题,或者证明不存在任何问题。Yato随后定义了ASP完备性,即很难找到其他解决方案的问题,并证明数独是ASP完备性的。因为他还证明了ASP完备性意味着NP难,这意味着如果你允许任意大小的数独板,就没有多项式时间算法来检查你生成的谜题是否有唯一的解(除非P=NP)


    很抱歉破坏了您对快速算法的希望

    我还认为您必须明确检查唯一性。如果你只有不到17个givens,那么一个独特的解决方案是不太可能的:还没有找到,尽管还不清楚它是否可能存在。)


    但您也可以使用SAT解算器,而不是编写自己的回溯算法。这样,您可以在某种程度上调节找到解决方案的难度:如果您限制SAT解算器使用的推理规则,您可以检查您是否可以轻松解决难题。只需谷歌搜索“SAT解数独”

    这里有一种制作经典数独游戏的方法(只有一种解决方案的数独游戏;预填充的方块围绕中心方块对称)

    1) 从一个完整的网格开始(使用组填充加循环移位以轻松获得)

    2) 如果可以使用剩余线索推断出清除的正方形,则从两个对称正方形中删除数字

    3) 重复(2)直到所有数字都被选中


    使用此方法,您可以创建一个非常简单的数独游戏,无论是否编程。你也可以用这个方法来制作更难的数独游戏。您可能希望在YouTube上搜索“创建经典数独”以获得一个分步示例。

    解决方案分为两部分:
    A.生成数字模式6000亿 B.生成掩蔽图案~7e23组合

    A)对于数字模式,最快的方法是生成具有NOint[][] board = new int[][] { {1,2,3, 4,5,6, 7,8,9}, {4,5,6, 7,8,9, 1,2,3}, {7,8,9, 1,2,3, 4,5,6}, {2,3,1, 5,6,4, 8,9,7}, {5,6,4, 8,9,7, 2,3,1}, {8,9,7, 2,3,1, 5,6,4}, {3,1,2, 6,4,5, 9,7,8}, {6,4,5, 9,7,8, 3,1,2}, {9,7,8, 3,1,2, 6,4,5} };
    void shuffleNumbers() {
            for (int i = 0; i < 9; i++) {
                int ranNum = random.nextInt(9);
                swapNumbers(i, ranNum);
            }
        }
    
    private void swapNumbers(int n1, int n2) {
            for (int y = 0; y<9; y++) {
                for (int x = 0; x<9; x++) {
                    if (board[x][y] == n1)
                        board[x][y] = 0;
                    if (board[x][y] == n2)
                        board[x][y] = n1;
                }
            }
    
            for (int y = 0; y<9; y++) {
                for (int x = 0; x<9; x++) {
                    if (board[x][y] == 0)
                        board[x][y] = n2;
                }
            }
        }
    
    void shuffleRows() {
            int blockNumber;
    
            for (int i = 0; i < 9; i++) {
                int ranNum = random.nextInt(3);
                blockNumber = i / 3;
                swapRows(i, blockNumber * 3 + ranNum);
            }
        }
    
    void swapRows(int r1, int r2) {
            int[] row = board[r1];
            board[r1] = board[r2];
            board[r2] = row;
        }
    
    void shuffleCols() {
            int blockNumber;
    
            for (int i = 0; i < 9; i++) {
                int ranNum = random.nextInt(3);
                blockNumber = i / 3;
                swapCols(i, blockNumber * 3 + ranNum);
            }
        }
    void swapCols(int c1, int c2) {
            int colVal;
            for (int i = 0; i < 9; i++){
                colVal = board[i][c1];
                board[i][c1] = board[i][c2];
                board[i][c2] = colVal;
            }
        }
    
    void shuffle3X3Rows() {
    
            for (int i = 0; i < 3; i++) {
                int ranNum = random.nextInt(3);
                swap3X3Rows(i, ranNum);
            }
        }
    
    void swap3X3Rows(int r1, int r2) {
            for (int i = 0; i < 3; i++) {
                swapRows(r1 * 3 + i, r2 * 3 + i);
            }
        }
    
    
    void shuffle3X3Cols() {
    
            for (int i = 0; i < 3; i++) {
                int ranNum = random.nextInt(3);
                swap3X3Cols(i, ranNum);
            }
        }
    private void swap3X3Cols(int c1, int c2) {
            for (int i = 0; i < 3; i++) {
                swapCols(c1 * 3 + i, c2 * 3 + i);
            }
        }
    
    func getNumberSudoku() -> [[Int]] {
        // Original number
        let originalNum = [1,2,3,4,5,6,7,8,9]
    
        // Create line 1 to 9 and shuffle from original
        let line1 = originalNum.shuffled()
        let line2 = line1.shift(withDistance: 3)
        let line3 = line2.shift(withDistance: 3)
        let line4 = line3.shift(withDistance: 1)
        let line5 = line4.shift(withDistance: 3)
        let line6 = line5.shift(withDistance: 3)
        let line7 = line6.shift(withDistance: 1)
        let line8 = line7.shift(withDistance: 3)
        let line9 = line8.shift(withDistance: 3)
    
        // Final array
        let renewRow = [line1,line2,line3,line4,line5,line6,line7,line8,line9]
    
        // Pre-shuffle for column
        let colSh1 = [0,1,2].shuffled()
        let colSh2 = [3,4,5].shuffled()
        let colSh3 = [6,7,8].shuffled()
        let rowSh1 = [0,1,2].shuffled()
        let rowSh2 = [3,4,5].shuffled()
        let rowSh3 = [6,7,8].shuffled()
    
        // Create the let and var
        let colResult = colSh1 + colSh2 + colSh3
        let rowResult = rowSh1 + rowSh2 + rowSh3
        var preCol: [Int] = []
        var finalCol: [[Int]] = []
        var prerow: [Int] = []
        var finalRow: [[Int]] = []
    
        // Shuffle the columns
        for x in 0...8 {
            preCol.removeAll()
            for i in 0...8 {
                preCol.append(renewRow[x][colResult[i]])
            }
            finalCol.append(preCol)
        }
    
        // Shuffle the rows
        for x in 0...8 {
            prerow.removeAll()
            for i in 0...8 {
                prerow.append(finalCol[x][rowResult[i]])
            }
            finalRow.append(prerow)
        }
    
        // Final, create the array into the [[Int]].
        return finalRow
    }
    
    var finalArray = [[Int]]
    finalArray = getNumberSudoku()
    
    #pz is a 9x9 numpy array
    def PossibleValueAtPosition(pz:[], row:int, col:int):
        r=row//3*3
        c=col//3*3
        return {1,2,3,4,5,6,7,8,9}.difference(set(pz[r:r+3,c:c+3].flat)).difference(set(pz[row,:])).difference(set(pz[:,col]))
    
    def SolvePuzzle(pz:[], n:int, Nof_solution:int):# init Nof_solution = 0
        if Nof_solution>1:
            return Nof_solution  # no need to further check
        if n>=81:
            Nof_solution+=1
            return Nof_solution
        (row,col) = divmod(n,9)
        if pz[row][col]>0:      # location filled, try next location
            Nof_solution = SolvePuzzle(pz, n+1, Nof_solution)
        else:
            l = PossibleValueAtPosition(pz, row,col)
            for v in l:         # if l = empty set, bypass all 
                pz[row][col] = v    # try to fill a possible value v  
                Nof_solution = SolvePuzzle(pz, n+1, Nof_solution)
                pz[row][col] = 0
        return Nof_solution     # resume the value, blacktrack