如何使用独特的解决方案生成数独板

时间:2011-08-03 09:20:47

标签: algorithm puzzle sudoku

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

13 个答案:

答案 0 :(得分:48)

这是我自己的SuDoKu程序的方式:


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

  2. 列出所有81个单元格位置并随机随机播放。

  3. 只要列表不为空,请从列表中取出下一个位置,然后从相关单元格中删除该号码。

  4. 使用快速回溯求解器测试唯一性。理论上,我的解算器能够计算所有解决方案,但是为了测试唯一性,它会在找到多个解决方案时立即停止。

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

  6. 如果当前电路板有多个解决方案,请撤消上次移除(步骤3),然后继续执行步骤3,并使用列表中的下一个位置

  7. 当你测试了所有81个位置时停止。


  8. 这不仅为您提供了独特的电路板,而且还为您提供了无法在不破坏解决方案独特性的情况下再删除任何数字的电路板。

    当然,这只是算法的后半部分。上半部分是首先找到一个完整有效的板(随机填充!)它非常相似,但“在另一个方向”:


    1. 从空白板开始。

    2. 在其中一个空闲单元格中添加一个随机数(该单元格是随机选择的,并且根据SuDoKu规则从该单元格的有效数字列表中随机选择该数字。)

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

    4. 重复直到电路板完全充满数字。

答案 1 :(得分:29)

简单:

  1. 使用高效的回溯算法找到所有解决方案。
  2. 如果只有一个解决方案,那么你就完成了。否则,如果您有多个解决方案,请找到大多数解决方案不同的位置。在此位置添加号码。
  3. 转到1.
  4. 我怀疑你能找到比这更快的解决方案。

答案 2 :(得分:16)

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

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

这些变化都不会使可解决的电路板无法解决。

答案 3 :(得分:10)

除非P = NP,否则没有多项式时间算法可以用一个解决方案生成一般的数独问题。

在他的硕士论文中,Takayuki Yato定义了The Another Solution Problem(ASP),其目标是,在给出问题和解决方案的情况下,找到针对该问题的不同解决方案或显示不存在的问题。 Yato然后定义了ASP完整性,难以找到另一个解决方案的问题,并表明Sudoku是ASP完成的。由于他也证明ASP完全性意味着NP硬度,这意味着如果你允许任意大小的数独板,没有多项式时间算法来检查你生成的拼图是否有一个独特的解决方案(除非P = NP)。

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

答案 4 :(得分:1)

提供通用解决方案并不容易。你需要知道一些事情才能生成特定类型的数独...例如,你不能建立一个超过9个空的9个数字组(行,3x3块或列)的数独。单一解决方案中的最小给定数字(即&#34;线索&#34;)数独被认为是17,但如果我没错,这个数独的数字位置非常具体。数独的线索平均数约为26,我不确定,但是如果你退出已完成网格的数字直到有26并且以对称的方式离开它们,你可能有一个有效的数独。 另一方面,您可以随机退出已完成网格中的数字并使用CHECKER或其他工具对其进行测试,直到确定为止。

答案 5 :(得分:1)

这是一种制作经典数独谜题的方法(数独谜题有一个唯一的解决方案;预先填充的方块围绕中心方块R5C5对称)。

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

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

3)重复(2)直到检查完所有数字。

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

答案 6 :(得分:1)

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

A)对于数字模式,这是最快的方法,它可以生成唯一的组合,而在回溯或测试上花费的时间 NO

第1步。选择一个已经存在的矩阵,我选择了下面的一个矩阵,因为它可以很容易地由人制作,而无需计算设备或求解器的任何帮助:

第一行是数字升序
第二行也按升序排列,但从4开始并滚动
第三行也按升序排列,但从7开始并滚动
第4、5、6行:将三格列替换为右上列-2 5 8,并在3x3格内滚动至最后一列
第7、8、9行:将三个单元格的列替换为右上方的列-3 6 9,并在3x3单元格内滚动至最后一列

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

第2步。将数字进行混洗并替换所有其他单元格中的
步骤3.在自己内部随机重新排列第1,2和3列
步骤4.在自己内部随机重新排列第4,5和6列
第5步。在自己内部随机重新排列第7,8和9列
第6步。在自己内部随机重新排列第1,2和3行
第7步。在自己内部随机重新排列第4,5和6行
第8步。在自己内部随机重新排列第7,8和9行
步骤9。随机重新排列3个大小为9x3的列组
第10步。随机重新排列3个大小为3x9的行组

瞧……

5 8 3 1 6 4 9 7 2
7 2 9 3 5 8 1 4 6
1 4 6 2 7 9 3 8 8
8 5 2 6 9 1 4 3 7
3 1 7 4 2 5 8 6 9
6 9 4 8 3 7 2 5 1
4 6 5 9 1 3 7 2 8
2 3 1 7 8 6 5 9 4
9 7 8 5 4 2 6 1 3

B)对于屏蔽模式,我们需要一个求解器算法。由于我们已经有一个非常独特的数字网格(也可以解决!),这使我们可以更快地使用求解器

第1步:首先从81个随机位置中选择15个。
步骤2:使用求解器检查其是否具有唯一的解决方案
步骤3:如果解决方案不是唯一的,请选择其他位置。重复执行第2步和第3步,直到找到唯一的解决方案

这应该为您提供非常独特且快速的数独板。

答案 7 :(得分:0)

我还认为你必须明确检查唯一性。如果你的得分少于17,那么一个独特的解决方案是不太可能的,但是:尚未找到,但目前尚不清楚它是否存在。)

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

答案 8 :(得分:0)

一种更快生成数独的方法。

  1. 找到一个存在的数独。
  2. 用随机组交换值。
  3. 交换单元格或列或行网格或列网格。

您交换值将使值有所不同,如果不交换行或列,则数独在本质上不会更改。

您可以用9个网格标记数独,交换的行和列必须在同一网格中进行。就像您可以交换行1-3,行4-6,行7-9,不要交换行1-4或行1-7。您还可以交换row-grid(将row1〜3替换为row4〜6或row7〜9)。

解决数独问题:记录所有可能的空值,然后检查1到9之间的值。如果一个值唯一,则将其从循环中删除。

答案 9 :(得分:0)

  1. 找到数独的数独板。 (使用琐碎的小工具或将其搜索到Google上,或者只使用商店中的任何数独应用即可)
int[][] 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}
        };
  1. 对于每个数字0至9(例如curNum),(即0、1、2、3、5、6、7、8)从范围(0至9)中取一个随机数,遍历木板,交换curNum和您的随机数。
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;
            }
        }
    }
  1. 现在可以随机排列行。取第一组3行,将它们洗净,然后对所有行进行处理。 (在9 X 9数独中),将其用于第二组和第三组。
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;
    }
  1. 交换列,再次获取3列的块,将它们洗牌,然后对所有3块进行处理
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;
        }
    }
  1. 交换行块本身(即3X9块)
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);
        }
    }

  1. 对列执行相同操作,逐块交换
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);
        }
    }

现在,您完成的木板应该是有效的数独木板

关于该算法的效率,花了3.6秒钟来生成一百万个木板

是的,您可以创建在整个流程类中使用的变量或全局变量,因此您无需一遍又一遍地进行设置,例如ranNum

如果要生成带有隐藏值的最终纸板,请阅读一些回溯数独算法,并尝试从纸板中移除一个元素,直到有了纸板为止,即使仅移除一个元素,也将无法解决。

如果您想按难度对最终生成的木板进行分类,只需在逐个删除元素时算出木板中还剩下多少个数字即可。越难解决的数字越少

一个提示,数独中最少可能的提示可以是17,但是所有可能的数独板不一定都可以简化为17提示数独

答案 10 :(得分:0)

SWIFT 5版本

简单的方法,这是我的代码:

首先,将函数创建到[[Int]]数组中

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()

答案 11 :(得分:0)

您可以从任何有效的(填充的)拼图开始,然后对其进行修改以产生完全不同的拼图(再次填充)。代替排列数字组,您可以交换单个单元格-种子难题和由此产生的难题之间不会有任何相似之处。我很早以前就用VB编写了一个简单的程序,您可以在这里找到它:https://www.charalampakis.com/blog/programming-vb-net/a-simple-algorithm-for-creating-sudoku-puzzles-using-visual-basic。可以轻松将其翻译成任何语言。

然后,随机并逐渐移出单元格,并检查拼图是否可解决并具有独特的解决方案。您还可以根据解决方案所需的规则按照难度对难题进行评分。继续操作,直到删除任何已知的单元格导致无法解决的难题。

HTH

答案 12 :(得分:0)

您可能需要这样的代码:

#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