如何生成具有独特解决方案的Sudoku板?我想的是初始化一个随机板,然后删除一些数字。但是我的问题是如何保持解决方案的唯一性?
Answers:
这是我自己的SuDoKu程序执行此操作的方式:
从完整的有效木板开始(用81个数字填充)。
列出所有81个单元格的位置,并随机洗牌。
只要列表不为空,请从列表中移至下一个位置,然后从相关单元格中删除该数字。
使用快速回溯求解器测试唯一性。从理论上讲,我的求解器能够计算所有解决方案,但是为了测试唯一性,当发现多个解决方案时,它将立即停止。
如果当前电路板仍然只有一种解决方案,请转到步骤3)并重复。
如果当前电路板有多个解决方案,请撤消上一次移除的操作(步骤3),然后从列表中的下一个位置继续执行步骤3。
测试完所有81个位置后停止。
这不仅为您提供了独特的电路板,而且还为您提供了在不破坏解决方案唯一性的情况下无法删除任何其他数字的电路板。
当然,这只是算法的后半部分。上半部分是首先找到一个完整的有效木板(随机填充!),其工作原理非常相似,但“方向相反”:
从一个空板开始。
在其中一个空闲单元格上添加一个随机数(随机选择该单元格,并根据SuDoKu规则从对该单元格有效的数字列表中随机选择该数字)。
使用回溯求解器检查当前电路板是否至少具有一种有效的解决方案。如果不是,请撤消步骤2,然后重复另一个数字和单元格。请注意,此步骤可能会自己产生完整的有效板,但绝不是随机的。
重复该操作,直到面板上完全充满数字为止。
(3) Use the solver to check if the current board has at least one valid solution.
如果您仅向一个空白板上添加一个字符(在第2步中),然后在中测试您的求解器(在第3步中),那我就有些困惑了,您实际上是在求解一个空白板上。我认为我的求解器不是那么好,更重要的是,如果它可以解决一个空局,那么获得有效解决方案的问题就已经解决了,我可以跳到第4步!
你可以作弊。从可以解决的现有Sudoku面板开始,然后摆弄它。
您可以将三个3x3块的任何行与任何其他行交换。您可以将三个3x3块的任何列与另一列交换。在每个块行或块列中,您可以交换单行和单列。最后,您可以对数字进行置换,以便在填充位置上存在不同的数字,只要置换在整个板上都保持一致即可。
这些更改都不会使可解决的电路板无法解决。
除非P = NP,否则就没有一种多项式时间算法可以使用一种解决方案来生成一般的数独问题。
Yato Takayuki在其硕士论文中定义了“另一个解决问题”(ASP)”,目标是在给定问题和解决方案的情况下,找到该问题的其他解决方案或表明不存在任何解决方案。Yato然后定义了ASP完整性,这是很难找到其他解决方案的问题,并表明Sudoku是ASP完整性的。由于他还证明了ASP完整性暗示着NP硬度,这意味着,如果允许任意大小的Sudoku板,就没有多项式时间算法可以检查生成的拼图是否具有唯一的解决方案(除非P = NP)。
很抱歉破坏您对快速算法的希望!
解决方案分为两部分:
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,直到找到唯一的解决方案
这应该为您提供非常独特且快速的数独板。
提供通用解决方案并不容易。您需要了解几件事才能生成特定种类的Sudoku ...例如,您不能用超过9个空的9位数组(行,3x3块或列)构建Sudoku。单一解决方案数独中的最小给定数字(即“线索”)被认为是17,但是如果我没记错的话,此数独的数字位置非常具体。Sudoku的平均线索数约为26,我不确定,但是如果您退出一个完整网格的数目直到拥有26个并以对称方式离开,则您可能拥有有效的Sudoku。另一方面,您可以从已完成的网格中随机退出数字,并使用CHECKER或其他工具对其进行测试,直到确定成功为止。
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}
};
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);
}
}
现在您完成了板应该是有效的数独板
至于该算法的效率,花费了3.6秒来生成一百万板
是的,您可以创建在整个流程类中使用的变量或全局变量,因此您无需一遍又一遍地进行修改,例如ranNum
如果要生成带有隐藏值的最终纸板,请阅读一些回溯数独算法,并尝试从纸板中移除一个元素,直到有了纸板为止,即使仅移除一个元素也将无法解决。
如果您想按难度对最终生成的木板进行分类,只需逐个删除元素,计算剩下的木板数。越难解决的数字
只是一个提示,数独中最少可能的提示可以是17,但是所有可能的数独板不一定都可以简化为17提示数独
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()
您可以从任何有效的(填充的)拼图开始,然后对其进行修改以产生完全不同的拼图(再次填充)。代替排列数字组,您可以交换单个单元格-种子难题和由此产生的难题之间不会有任何相似之处。我很早以前用VB编写了一个简单程序,您可以在这里找到它:https : //www.charalampakis.com/blog/programming-vb-net/a-simple-algorithm-for-creating-sudoku-puzzles-using -视觉基础。它可以轻松地翻译成任何语言。
然后,随机逐步删除单元格,并检查拼图是否可解决并具有独特的解决方案。您还可以根据解决方案所需的规则按照难度对难题进行评分。继续直到删除任何已知的单元格导致无法解决的难题。
高温超导