数独生成
项目Github地址 (https://github.com/Tim-xiaofan/sudoku.git)
生成思路:通过已有的合法阵列进行变换
-
通过第一行循环右移(注意左上角的数字已确定,这里为1)
//偏移值
int offset[9] = { 0,3,6,1,4,7,2,5,8 };
例如第一行为1 2 3 4 5 6 7 8 9,经过一次右移的第二行例如第一行为1 2 3 4 5 6 7 8 9,经过一次右移的第二行例如第一行为 7 8 9 1 2 3 4 5 6 。 剩余行依此类推,得到一个合法数独阵列
也就是说在这种生成规则下,第一行的不同组合数量就决定了中的阵列数量。由于左上角的数字1是固定的,故有8!= 40320种(4万)。而需求是10 ^6(100万),远远不够。考虑再次基础上继续变换 -
三行一组,组内任意两行互换(123)、(456)、(789)
例如上图图阵列, 交换8,9行位置后,如下图,依然合法。
在不考虑第一组的情况下,每种终局可以生成3!3!=36种全新的终局阵列。这样就总共有3640320=1451520>100w。
3.实现
(1)数据结构:先试试9乘9二维数组。
(2)不重复:这里理解为两点,一是某一次终局文件中没有重复的阵列,一是相同参数下不同时间终局文件不重复(可能有相同数独阵列)
(3)生成步骤:第一步,生成一个原始阵列;第二步,通过原始阵列进行变换,生成新的阵列;第三步,重复一、二步,直到满足数量为止。
(4)第一步的实现思路:**要生成一个原始阵列只需确定第一行,即从8!中取出一种情况。**但是如果8!中有一种情况已经使用过,就不能再使用了。为了避免这种情况,考虑从现有的排列生成新的排列。
查阅资料发现C++有相应的库,叫做STL库,库中实现了由当前排列求下一个排列的功能。
项目中只需简单的利用,参考博客https://blog.youkuaiyun.com/x_iaow_ei/article/details/28254413写了下面的demo
#include
#include
using namespace std;
const int N = 5;
const int len = 3;
int main()
{
int a[3]={3,4,5};
for(int i = 0; i < N; i++){
next_permutation(a, a+3); //下一个全排列,当没有下一个全排列时,函数返回值为0,
for(int i = 0; i < len; i++){ //并变为字典序最小的排列,否则函数返回值为1
cout << a[i];
}
cout <<" ";
}
return 0;
}
上面的代码输出了所有排列
关于下一个排列和上一个排列理解如下(摘自博客https://www.cnblogs.com/aiguona/p/7304945.html):
“下一个排列组合”和“上一个排列组合”,对序列 {a, b, c},每一个元素都比后面的小,按照字典序列,固定a之后,a比bc都小,c比b大,它的下一个序列即为{a, c, b},而{a, c, b}的上一个序列即为{a, b, c},同理可以推出所有的六个序列为:{a, b, c}、{a, c, b}、{b, a, c}、{b, c, a}、{c, a, b}、{c, b, a},其中{a, b, c}没有上一个元素,{c, b, a}没有下一个元素。
生成第一行的的问题解决了(如何生成和避免重复),那么接下来就是换行变换了**。考虑到在第一组(123行)不变换的情况下已经满足需求,这里只进行456和789两组的变换。记为A组,B组。
那怎么变换呢?
先考虑A组,总共有456 465 546 564 645 654。然后考虑B组,有789 798 879 897 978 987 。每确定一个A的排列,B组有对应的不同6中排列。如此反复,可以变换出36种。大致如下操作
for(int i = 0; i < 6; i++){
确定为A[i];
for(int j = 0; j < 6; j++){
确定为B[j];
保存或输出当前数独。
}
}
这样换行变换就解决了。
具体代码实现:
void newFromModel();方法
//在原始阵列基础上进行变换并保存36个排列
void SudokuFactory::newFromModel() {
//cout << "newFromModel()\n";
int A[3] = { 4, 5, 6 };//456为一组
int B[3] = { 7, 8, 9 };//789为一组
//前三行不变
char firstThreeRows[N * 6];// 保存123行
int index = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < N - 1; j++) {
firstThreeRows [index++]= (model[i][j]);
firstThreeRows[index++] = ' ';
}
firstThreeRows[index++] = (model[i][N - 1]);
firstThreeRows[index++] = '\n';//换行
}
//cout << sudokuString;
for (int a = 0; a < 6; a++) {
//确定A组456,的一个排列
//cout << "!!!!!!!!!!A:\n";
//printArray(A, 3);
char midThreeRows[6 * N];//保存456行
int index = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < N - 1; j++) {
//每生成一个数字,直接保存。注意空格和换行。
//cout << "(" << sudokuArray[A[index] - 1][j] << ")";
midThreeRows[index++] = (model[A[i] - 1][j]);
midThreeRows[index++] = ' ';
//cout <<"\n<"<< sudokuString << ">" << endl;
//cout << sudokuString;
}
//cout << "(" << sudokuArray[A[index] - 1][N - 1] << ")" << endl;
midThreeRows[index++] = (model[A[i] - 1][N - 1]);//行尾没有空格
//cout << "\n<" << sudokuString << ">" << endl;
midThreeRows[index++] = '\n';//换行
}
//cout << "transA:";
//cout << "\n<" << sudokuString << ">" << endl;
//同一个A的排列可以有6种不同的排列
for (int b = 0; b < 6; b++) {
//确定B组789,的一个排列
//cout << "B:\n";
//printArray(B, 3);
char lastThreeRows[6 * N];
int index = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < N - 1; j++) {
//每生成一个数字,直接保存。注意空格和换行。
//cout << "(" << sudokuArray[B[index] - 1][j] << ")";
lastThreeRows[index++] = (model[B[i] - 1][j]);
lastThreeRows[index++] = ' ';
}
//cout << "(" << sudokuArray[B[index] - 1][N - 1] << ")" << endl;
lastThreeRows[index++] = (model[B[i] - 1][N - 1]);//行尾没有空格
lastThreeRows[index++] = '\n';//换行
//cout << sudokuString;
}
//剩余需求量变化-1
need--;
//直接输出到文件
store(firstThreeRows,midThreeRows,lastThreeRows);
//cout << "need = " << need << endl;
//cout << "transB:";
//cout << oneSudokuString;
if (need == 0) return;//为零则,结束生成
//cout << "\n";//添加数独阵列间空行
sudokuStore[index_store++] = '\n';
//B的下一个排列
next_permutation(B, B + 3);
}
//A的下一个排列
next_permutation(A, A + 3);
}
}
代码简要说明:
其中N=9,二维数组sudokuArray保存当前的原始数独(模板数独),数组A,B分别记录456组和789组的变化情况,例如现在A[3]={4, 6, 5},即第五行和第六行进行了交换:
当我们要确定新的数独的第4行时,我们读取A[0],发现值为4,表明第4行与原始数独第4行一致,直接存入;当我们要确定新的数独的第5行时,我们读取A[0],发现值为6,表明第4行应该为模板数独的第6行数据,这是存入模板数独第六行即可。第六行同理。
789行也是同上操作。
当一个模板数独全部用完时,依然没有满足要求,我们就要生成新的模板数独,调用 上面的方法继续生成即重复**(a)生成模板数独(b)由模板数独进行变换**这两个步骤,知道满足需求为止。
补充说明,我们知道I/O操作是十分耗时的,为了减少I/O操作的次数,我们这里把结果先统一存入一个叫sudokuFileString的字符窜中,然后一次性输出到文件。
数独终局生成(3)