数独终局生成(2)

数独生成

项目Github地址 (https://github.com/Tim-xiaofan/sudoku.git)

生成思路:通过已有的合法阵列进行变换
  1. 通过第一行循环右移(注意左上角的数字已确定,这里为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万),远远不够。考虑再次基础上继续变换

  2. 三行一组,组内任意两行互换(123)、(456)、(789)
    例如上图图阵列, 交换8,9行位置后,如下图,依然合法。
    在这里插入图片描述
    在不考虑第一组的情况下,每种终局可以生成3!3!=36种全新的终局阵列。这样就总共有3640320=1451520>100w。
    3.实现
    (1)数据结构:先试试9乘9二维数组。
    (2)不重复:这里理解为两点,一是某一次终局文件中没有重复的阵列,一是相同参数下不同时间终局文件不重复(可能有相同数独阵列)
    (3)生成步骤:第一步,生成一个原始阵列;第二步,通过原始阵列进行变换,生成新的阵列;第三步,重复一、二步,直到满足数量为止。
    (4)第一步的实现思路:**要生成一个原始阵列只需确定第一行,即从8!中取出一种情况。**但是如果8!中有一种情况已经使用过,就不能再使用了。为了避免这种情况,考虑从现有的排列生成新的排列。
    查阅资料发现C++有相应的库,叫做STL库,库中实现了由当前排列求下一个排列的功能。
    项目中只需简单的利用,参考博客https://blog.csdn.net/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)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值