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


70

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

Answers:


33

简单:

  1. 使用高效的回溯算法查找所有解决方案。
  2. 如果只有一种解决方案,那么您已经完成。否则,如果您有多个解决方案,请找到一个大多数解决方案都不相同的位置。在此位置添加号码。
  3. 转到1。

我怀疑您会找到比这更快的解决方案。


1
我认为您是对的,但是如何为以这种方式生成的borad分级,似乎没有参数可以控制难度。
桂林桂林

好吧,这是一个不同的问题,难度更大。可以确定的是,您添加的号码越多,越容易。
TMS

3
无需查找所有解决方案,只需寻找第二个解决方案就足够了。
戴维·霍瓦斯

59

这是我自己的SuDoKu程序执行此操作的方式:


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

  2. 列出所有81个单元格的位置,并随机洗牌。

  3. 只要列表不为空,请从列表中移至下一个位置,然后从相关单元格中删除该数字。

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

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

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

  7. 测试完所有81个位置后停止。


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

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


  1. 从一个空板开始。

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

  3. 使用回溯求解器检查当前电路板是否至少具有一种有效的解决方案。如果不是,请撤消步骤2,然后重复另一个数字和单元格。请注意,此步骤可能会自己产生完整的有效板,但绝不是随机的。

  4. 重复该操作,直到面板上完全充满数字为止。


我发现您的算法特别简单有效。谢谢。
krawyoti

4
(3) Use the solver to check if the current board has at least one valid solution. 如果您仅向一个空白板上添加一个字符(在第2步中),然后在中测试您的求解器(在第3步中),那我就有些困惑了,您实际上是在求解一个空白板上。我认为我的求解器不是那么好,更重要的是,如果它可以解决一个空局,那么获得有效解决方案的问题就已经解决了,我可以跳到第4步!
The111 2013年

@ The111:解决一个空板很容易,即使没有计算机也可以做到这一点。但是,我要寻找一个随机填充板,这就是为什么我不只是第3步后停止
布朗博士

第二算法中第三点的目的是什么?是否可以生成没有任何解决方案的有效电路板?
路加福音

@Luke:随便拿一个数独,就用一个解决方案。假设左上角是自由的,并且如果您仅应用规则(简而言之:每行,每列和3x3正方形均应包含数字1-9),您会直接发现可以放置1,3, 5和7插入左上角。但是最终解决方案中仅允许使用“ 1”,因此,如果放置3,5或7,则回溯求解器将显示这三个数字不会导致有效的最终解决方案。
布朗

19

你可以作弊。从可以解决的现有Sudoku面板开始,然后摆弄它。

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

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


但是唯一性呢?您如何选择空白单元格以保持解决方案唯一?
2011年

1
@kvphxga:您首先使用具有独特解决方案的部分电路板。任何允许的交换都不会影响解决方案的唯一性。
rossum 2011年

这不是一个可怕的解决方案吗?如果您使用一个完整的数独板,交换行和列,求解器是否会注意到难题之间的相似性(感觉相同)?您最终只能使用数量很少的独特解决方案,而且我担心在某些时候它不会对求解器产生随机影响。可能值得为此做的更好。
bryc

您可以交换行/列中的各个行,还可以将数字重新分配给位置。如果需要,可以说有十个不同的起始网格,然后随机选择一个。
rossum

12

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

Yato Takayuki在其硕士论文中定义了“另一个解决问题”(ASP)”,目标是在给定问题和解决方案的情况下,找到该问题的其他解决方案或表明不存在任何解决方案。Yato然后定义了ASP完整性,这是很难找到其他解决方案的问题,并表明Sudoku是ASP完整性的。由于他还证明了ASP完整性暗示着NP硬度,这意味着,如果允许任意大小的Sudoku板,就没有多项式时间算法可以检查生成的拼图是否具有唯一的解决方案(除非P = NP)。

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


3
公平地说,您可以使用所选答案中的技术每秒生成数百个独特的拼图。
爷爷

1
好吧,在这种情况下,我想看看。因为如果您尝试生成恶魔般的数独,有时测试所有可能的可能性确实很长。对于具有很多初始填充数字的简单数独,我同意。
染料

直到我仔细阅读本文开头(谢谢!),我对快速Zebra拼图生成器的希望几乎消失了。在求解器中,您需要找到一个解决方案(因此称为“求解器”),而在生成器中,则需要生成难题–您不需要实际解决它(大多数方法将求解器用作生成器的一部分的事实是另一回事)。 。我并不是说您的第一句话是错误的,我不是说那篇论文没有证明。
greenoldman

6

解决方案分为两部分:
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 5
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,直到找到唯一的解决方案

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


我花了一些时间想,但我认为现在已经有了。步骤2意味着例如将所有的1更改为5,将2更改为1。步骤3-8表示您可以重新排列行和列,只要它们位于相同的正方形即可。步骤9和步骤10表示重新排列正方形的行和列。我说对了吗?
迈克尔·萨蒙

1
该算法只会产生非常特殊的难题。如您所见,数字(5、8、3)始终作为一组出现在第1、2和3行中。其他所有3组也一样。遗憾的是,对于通用数独生成器而言,此算法没有用。
Jan Feldmann

1

提供通用解决方案并不容易。您需要了解几件事才能生成特定种类的Sudoku ...例如,您不能用超过9个空的9位数组(行,3x3块或列)构建Sudoku。单一解决方案数独中的最小给定数字(即“线索”)被认为是17,但是如果我没记错的话,此数独的数字位置非常具体。Sudoku的平均线索数约为26,我不确定,但是如果您退出一个完整网格的数目直到拥有26个并以对称方式离开,则您可能拥有有效的Sudoku。另一方面,您可以从已完成的网格中随机退出数字,并使用CHECKER或其他工具对其进行测试,直到确定成功为止。


2
最小线索的数量已证明2b 17 :)
rhbvkleef 2015年

我想补充一点,由于讨论的结果,事实证明,保证独特解决方案所需的最少预填充单元数问题已达17个。(当然,这并不意味着每个板都可以简化)。到17个单元:这仅表示没有具有独特解决方案的16个预填充单元的数独板,并且至少有一个具有独特解决方案的17个预填充单元的板。)
Sebastian

1

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

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

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

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

使用此方法,无论是否进行编程,您都可以创建一个非常简单的数独难题。您也可以使用此方法来制作更难的数独难题。您可能需要在YouTube上搜索“创建经典数独”,以逐步介绍示例。


1
  1. 找到数独的数独板。(使用琐碎的工具或将其搜索出来,或只使用商店中的任何数独应用)
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提示数独


1

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

0

我还认为您将必须明确检查唯一性。但是,如果您的赠予少于17个,那么就不可能有唯一的解决方案:尽管还不清楚是否可能存在,但尚未找到。)

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


0

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

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

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

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

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


0

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

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

高温超导

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.