Sudoku——软工个人项目

**

问题概述:

**
任务:实现一个能够生成数独终局并且能求解数独问题的控制台程序。
1.生成不重复的数独终局至文件。
2.读取文件内的数独问题,求解并将结果输出到文件。

代码仓库地址:个人项目
基本思路:

拿到题目的时候其实没有看懂到底要求做什么,逮着室友问了之后终于了解了问题是什么。
整个项目分为两个部分,第一部分为生成数独,即为输入命令,创建一个txt文件,然后将代码生成的相应数量的数独按照一定格式写入这个txt文件。
第二部分为求解数独,即为输入命令,读取指定文件的数独问题(有挖空的数独),然后求解,再将得到的数独终局写入另一个txt文件中(与第一部分生成数独的文件相同,内容覆盖)。

生成数独终局部分
一开始想到用一个初始的数独终局,考虑标题左上角的数字固定,然后单个数字替换法(7+6+5+4+3+2+1=33种)加上行列交换(77=49种),但这样只能产生3349=1617种终局,远远不够题目要求的1~1e6种。如果用单纯的随机数生成,在判断是否重复上肯定又要花费大量时间。在网上找资料之后发现了另一种思路: 用一个1-9无重复的排列有规律地进行平移,可以产生一个完整数独终局。即用第一行即可用平移来生成其余的2-9行。因为要保证左上角数字固定,平移的间隔为3。首行第一个数字固定为9,向右分别平移3、6、1、4、7、2、5、8位的示例如下。

9 1 2 3 4 5 6 7 8
3 4 5 6 7 8 9 1 2
6 7 8 9 1 2 3 4 5
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
第一行可以认为平移了0位。前三行的第一行锁死不能移动,因此只有第二行和第三行可以进行一次交换,即为只有“036”和“063”两种平移方式。
其余456行和789行在内部各可以进行{“147”、“174”、“417”、“471”、“714”、“741”}和{“258”、“285”、“528”、“582”、“825”、“852”}的六种平移方式。平移方式还可以进行组合,所以共能组合出266=72种不同的方式。
每个无重复的1-9排列可以生成72种终局,而排列可以有8!=40320种,即总共可以产生72*8!=2903040种终局,满足1~1e6的数量范围要求。
重要变量
char Data[10] = { ‘1’,‘2’,‘3’,‘4’,‘5’,‘6’,‘7’,‘8’ }; :初始存放除了固定数字之外的八个数字,后来要将固定数字加到排列尾部。
char Sudoku[200000002];:一个极大的一维字符串数组,采用这种方法存储最终打印的数独数据能大幅降低系统运行时间,因为系统会将很多时间花在处理文件上。

重要函数
void BuildMap();:生成总体的数独图。

voidBuildSudoku(char rule1, char rule2, char* rule3);**:生成一个数独。

void ThreeCows(char rule);*:按照传递的规则生成三行排列。

求解数独终局部分
基本思想当然是回溯。网上查找资料后,解题思路如下:
重要变量声明
char Rule[3][10][10]:一个型三维数组,第一位为0时代表行,此时第二位代表行号,第三位代表1-9的某个数字;第一位为1时代表列,此时第二位代表列号;第三位为2时代表宫,此时第二位为宫号。通过Rule数组我们可以查找当前位置(空格)上某个数字是否符合填入规则(即在本行、本列、本宫,都没有出现过这个数字),
char State[9][9]:一个二维数组,代表了数独的当前分布状态。

重要函数

void ReadSudokuFile(int row,int string); :读取写有数独问题的文件。

void CheckRule(int row,int column,int num);:用于检查当前位置数字num是否符合填入规则。

void SetRule(int row,int column,int num,bool state);:用于标明当前位置数字num是否被占用。state为flase时表示未被占用,为true时表示已被占用。

void SolveSudoku(int row,int column);:求解数独的核心部分,用递归来试探填入数字寻求解,如果找不到就擦除搜索标记和一步步回到可以重新填入的地方。
**
遇到问题:
1.在命令行界面输入命令无效,如图所示。
在这里插入图片描述
原本以为是Sudoku文件有问题无法运行,折腾好久才想起可能是没有定位到exe文件所在目录。解决途径(win10):输入磁盘号定位到文件所在磁盘—>用cd定位到exe文件所在目录—>输入指定命令。
在这里插入图片描述
2.在生成首行排列时费了很多时间想去写循环,但发现单纯的循环来实现排列组合需要的参数太多,又复杂又容易出错。后来用了STL标准模板库的next_permutation函数来实现升序全排列,非常省事。其函数原型为

#include<algorithm>
bool next_permutation(iterator start,iterator end);

next_permutation(num,num+n)即为对数组num中的前n个元素进行全排列,同时改变num数组的值。

3.当数量很大时,程序不运行。
在测试的时候,指定数量为666666,命令行不输出我所要的答案。检查了NeedCount、NowCount等相关变量的类型,都为long long int,那么问题在哪儿呢?经过测验发现是我用来存储数独的数组开得不够大。之前用的是100000002,改为200000002后可以正常输出。
错误情况:

输入:Sudoku.exe -c 666666
输出:Great! Now we begin to create sudoku!
NeedCount:666666

正确情况:

输入:Sudoku.exe -c 1000000
输出:Great! Now we begin to create sudoku!
NeedCount:1000000
You use 2.731 seconds to build these sudoku!

更新ing

代码部分:
1.生成1-9的排列

void ThreeCrows(char* rule){
	for(int i = 0; i < 3; i++){
		Sudoku[datap++] = Data[(8 + rule[i] - '0') % 9];
		for(int j = 1; j < 17; j++){
			Sudoku[datap++] = ' ';//相邻数字之间的空格 
			j++;
			Sudoku[datap++] = Data[((16 - j) / 2 + rule[i] - '0') % 9];//数组 
		}
		Sudoku[datap++] = '\n';//每一行数字的换行 
	}
}
void BuildSudoku(char* rule1,char* rule2,char* rule3){ 
	ThreeCrows(rule1);
	ThreeCrows(rule2);
	ThreeCrows(rule3);
	Sudoku[datap++] = '\n';//数独之间的换行
}

2.生成总的数独终局

void BuildMap(){
	char rule1[10][10] = {"036","063"};
	char rule2[10][10] = {"147","174","417","471","714","741"};
	char rule3[10][10] = {"258","285","528","582","825","852"};
	do{
		Data[8] = '9'; //插入固定数字到末尾,这里为(6+2)%9+1 = 9 
		for(int i = 0; i < 2; i++){
			for(int j = 0; j < 6; j++){
				for(int k = 0; k < 6; k++){
					BuildSudoku(rule1[i],rule2[j],rule3[k]);
					NowCount++;
					if(NowCount == NeedCount){
						return ;
					}
				}
			}
		}
	}while(next_permutation(Data,Data + 8));//next_permutation函数使用前需要对排列进行升序排序 ,否则只能找出该排列之后的全排列 
}

3.求解数独的递归函数

void SolveSudoku(int row, int column) {
	bool IsSearch = false;
	while (State[row][column] != '0') {//循环直到找到一个数独问题的空格 
		if (column < 8) {
			column++; //列号小于8则列号+1,检查本行下一位数字 
		}
		else {
			column = 0;//否则将列号置零,行号+1,检查下一行 
			row++;

		}
		if (row == 9) {//如果循环查找完毕都没有找到空格,表明数独的解已被找到 
			IsFindAns = true;
			return;
		}
	}
	for (int i = 1; i <= 9; i++) {
		if (CheckRule(row, column, i)) {//如果数字i在当前位置符合填入规则 ,将其填入此空格 
			SetRule(row, column, i, 1);//标明当前位置该数字 i 已被占用 
			State[row][column] = i + '0';//更新当前数独状态的数组 
			IsSearch = true;
			SolveSudoku(row, column);//递归调用SolveSudoku函数 ,查找下一个空格位置可能的解 
		}
		if (IsSearch) {//如果已被搜过,没有进入递归(没有找到可以填入的数字),那么开始回溯 
			IsSearch = false;
			if (IsFindAns) {
				return;
			}
			else {
				State[row][column] = '0';
				SetRule(row, column, i, 0);//将原本填入的数字擦掉,同时消掉该数字的占用标记 
			}
		}
	}
}

4.规则函数

void SetRule(int row, int column, int num, bool state) {
	Rule[0][row][num] = state;
	Rule[1][column][num] = state;
	Rule[2][gong][num] = state;
}
bool CheckRule(int row, int column, int num) {
	if (Rule[0][row][num] == 0 && Rule[1][column][num] == 0 && Rule[2][gong][num] == 0) {//如果在本行、本列、本宫,数字num都没有出现 
		return true;//那么可以填入 
	}
	else {
		return false;
	}
}

5.最终的main函数

int main(int argc, char* argv[])
{
	clock_t start;
	clock_t finish;
	double duration;
	if (argc == 3 && strcmp(argv[1], "-c") == 0) {
		for (int i = 0; i < strlen(argv[2]); i++) {
			if (argv[2][i] >= '0' && argv[2][i] <= '9') {
				NeedCount *= 10;
				NeedCount += argv[2][i] - '0';
			}
			else {
				cout << "Input error! Please enter the number in right format!" << endl;
				return 0;
			}
		}

		cout << "Great! Now we begin to create sudoku!" << endl;
		cout << "Needcount:" << NeedCount << endl;

		start = clock();
		while (NowCount < NeedCount) {
			BuildMap();
		}
		Sudoku[datap++] = '\n';//每个组数独之后的换行 
		WriteBuildSudoku();
		finish = clock();
		duration = (double)(finish - start) / CLOCKS_PER_SEC;
		cout << "You use " << duration << " seconds to build these sudoku!" << endl;
	}
	else if (argc == 3 && strcmp(argv[1], "-s") == 0) {
		cout << "Oh!Let's start to get these sudoku's solutions!" << endl;
		FILE *fp1 = fopen(argv[2], "r");
		char string[100];
		int rowline = 0;
		start = clock();
		while (fgets(string, 20, fp1)) {
			if (strcmp(string, "\n") == 0) {
				continue;
			}
			ReadSudokuFile(rowline, string);
			rowline++;
			if (rowline == 9) {
				if (!IsFirstSudoku) {//不是第一个数独问题则打印一个换行
					OutputSolveFile << endl;
				}
				else {
					IsFirstSudoku = false;
				}
				IsFindAns = false;
				SolveSudoku(0,0);
				rowline = 0;
				WriteOneSudoku();
				memset(Rule, 0, sizeof(Rule));//每次求解完一个数独后将Rule重置归零 
			}
		}
		finish = clock();
		duration = (double)(finish - start) / CLOCKS_PER_SEC;
		cout << "You use " << duration << " seconds to solve these sudoku!" << endl;
	}
	else {
		cout << "Input error! Wrong format!" << endl;
	}
	system("pause");
	return 0;
}

性能分析

VS2017的分析—>性能探查器—>性能向导—>检测—>一个或多个可用项目—>Sudoku—>完成。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

茶如影

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值