如何生成一个只有唯一解的数独棋盘?我的想法是初始化一个随机的棋盘,然后去掉一些数字。但我想知道如何保持解的唯一性?
这是我自己的SuDoKu程序实现的方式:
从一个完整、有效的棋盘开始(填有81个数字)。
制作一个所有81个单元格位置的列表,并随机打乱它。
只要列表不为空,就从列表中获取下一个位置并从相关单元格中删除数字。
使用快速回溯求解器测试唯一性。我的求解器在理论上能够计算所有的解,但为了测试唯一性,当它发现多于一个解时就立即停止。
如果当前棋盘仍然只有一个解,请执行第3步并重复。
如果当前棋盘有多个解,请撤销最后一次删除(第3步),并继续使用列表中的下一个位置执行第3步。
当您测试完所有81个位置时停止。
这不仅会给您独特的棋盘,还会给您不能再删除任何数字而不破坏解唯一性的棋盘。
当然,这只是算法的后半部分。首先要完成第一部分,即先找到一个完整的有效棋盘(随机填充!)它的实现方式非常类似,但“方向相反”:
从空棋盘开始。
在一个自由单元格中添加一个随机数(单元格是随机选择的,数字是从根据SuDoKu规则对该单元格有效的数字列表中随机选择的)。
使用回溯求解器检查当前棋盘是否至少有一个有效解。如果没有,请撤销第2步并在另一个数字和单元格中重复执行。请注意,此步骤可能会独立地产生完整的有效棋盘,但这些棋盘并不是随机的。
重复直到棋盘被完全填满数字。
简单:
我怀疑你能找到比这更快的解决方案。
你可以作弊。从一个能够解决的数独游戏板开始,然后篡改它。
你可以交换任意一行三个3x3块中的行与另一行。你也可以将任意一列三个3x3块中的列与另一列互换。在每个块行或块列内,你可以交换单行和单列。最后,你可以排列数字,使填充位置有不同的数字,只要排列在整个板上是一致的即可。
这些改变都不会使本来可以解决的数独游戏板成为无法解决的。
解决方案分为两部分:
A. 生成数字组合模式6000亿
B. 生成屏蔽模式约7e23个组合
A)对于数字模式,最快的方法是生成独特组合,不需要花费时间进行回溯或测试。
步骤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
完成了...
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,直到找到唯一解。
这将为您提供非常独特且快速的数独游戏板。
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] = n2;
} else if (board[x][y] == n2) {
board[x][y] = n1;
}
}
}
}
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);
}
}
现在你已经完成了,你的数独板应该是一个有效的数独板
要生成一个带有隐藏数值的数独板,可以使用回溯数独算法,从板上尝试移除一个元素,直到你拥有一个可解的数独板,并继续移除直到只要再移除一个元素就会变得不可解。
如果你想根据难度分类最终生成的数独板,只需在一次次移除元素时计算板上剩下的数字数量。数字越少,难度越大。
数独最少可能的提示为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()
提供一个通用的解决方案并不容易。你需要知道一些事情才能生成特定类型的数独...例如,你不能构建一个有超过九个空的9数字组(行、3x3块或列)的数独。单解数独中最少给出的数字(即“线索”)被认为是17,但如果我没记错的话,这个数独的数字位置非常具体。数独的平均线索数量约为26,但我不确定,如果你从完成的网格中退出数字直到有26个,并以对称的方式留下这些数字,那么你可能会得到一个有效的数独。 另一方面,你可以随机地从完成的网格中退出数字,并使用CHECKER或其他工具进行测试,直到它变成OK。
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 4 5 6 7 8 9 1 5 6 7 8 9 1 2 3 4 8 9 1 2 3 4 5 6 7 3 4 5 6 7 8 9 1 2 6 7 8 9 1 2 3 4 5 9 1 2 3 4 5 6 7 8
- Francisco Ary Martins9 6 1 2 5 4 7 8 3 2 5 4 7 8 3 9 6 1 ...
- Francisco Ary Martins