软件工程个人项目——数独程序

项目地址

GitHub项目地址:https://github.com/lostcake/ZJQ
(这个项目的名字当时创建后改不过来了TAT……)

PSP表格

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划
·Estimate·估计这个任务需要多少时间3030
Development开发
·Analysis·需求分析(包括学习新技术)5040
·Design Spec·生成设计文档9060
·Design Review·设计复审(和同学审核设计文档)1010
·Coding Standard·代码规范(为目前的开发指定合适的规范)3020
·Design·具体设计120150
·Coding·具体编码15001200
·Code Review·代码复审120120
·Test·测试(自我测试,修改代码,提交修改)150120
Reporting报告
·Test Report·测试报告150120
·Size Measurement·计算工作量3020
·Postmortem & Process Improvement Plan·事后总结,并提出过程改进计划6090
合计23401980

解题思路

最初步的想法自然是采用深度优先遍历的方法,但是因为至多需要生成100万个数独,单纯采用深度优先遍历恐怕会严重超时无法完成。但在采用优化方法前,暂且采用这个简单方法简化思路是可行的。

因此我们可以将这个项目分为三部分:输入与输入检查部分创建数独部分求解数独部分

首先从输入入手。由于我的代码基础比较薄弱,在这里还多花费了一点时间。通过搜索知道了命令行程序的正确运行方法,需要使用main函数的参数argc和argv。了解了这一点就可以继续进行编写。我们需要输入的有三个参数,第一个参数是程序的地址,在程序中不必考虑;第二个参数是-c或者-s,-c表示创建数独,-s表示解数独;第三个参数在创建数独时表示需要创建的数量,在解数独时表示待解决文件的地址。

接下来是最为复杂的创建数独部分。由于这一部分有性能要求,且数据量很大,因此需要慎重处理。我们采用一种比较简单的数独生成方式:

①首先创建一个1-9的数列,例如:
(1,2,3,4,5,6,7,8,9)
在第一位固定的情况下,这种方法能够生成8!=40320个不同的数列。

将此数列三个三个为一组,即A.(1,2,3)、B.(4,5,6)、C.(7,8,9)三部分。可以按照ABC、BCA、CAB的方式构造三排。即:
(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)

取上述三排的每一列,三个三个为一组,例如A.(1,4,7)、B.(2,5,8)、C.(3,6,9)。接下来按照ABC、BCA、CAB的方式构造每一列。构造后的结果为:
(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)
采用这种方式只需要构造一个数列就可以构造出一个数独。用这种方式可以生成40320个不同的数独,还达不到要求。因此需要继续进行扩增。

采用行交换的方式。1-3、4-6、7-9的行任意交换即可构造出一个不同的数独。由于首位数字固定,因此我们取2、3行交换。令九行为ABCDEFGHI,即可以有:
A(BC)(DEF)(GHI)
括号内的组相互交换,因此不同的变换方法有:
C 2 2 × C 3 3 × C 3 3 = 72 \mathrm{C}_2^2×\mathrm{C}_3^3×\mathrm{C}_3^3=72 C22×C33×C33=72因此通过变换我们总共可以构造出72×40320=2903040,达到了要求。因为最大数目为一百万,因此(BC)可以不必变换就可以达到数目(36×40320=1451520)。
综上,通过①到③步构建的数独再通过随机变换456行和789行就可以得到足够数目的数独。

最后是求解数独部分。首先从文档中读取数据,九行九行地取入数组。接下来开始依次寻找数组中的0,寻找能填入的数字。递归地进行这一过程,就可以找到解。最后将解输出即可。

设计实现过程

按照思路,设计实现也大致分为三部分。

1.输入与输入检查部分

这一部分可以直接写在主函数里:

①首先检查输入的参数数目,如果不是3则输出“参数数量错误”的提示;

②接下来检查第二个参数,如果不是“-c”或“-s”则输出“请使用正确的指令(‘-c’或‘-s’)”;

③如果第二个参数是“-s”,则检查第三个参数,取出第三个参数作为文件名。接下来尝试打开该文件,如果打开失败则输出“找不到该文件”的提示。否则就开始求解,转向第三部分;

④如果第二个参数是“-c”,则开始对第三个参数进行检查。一边检查一边将字符型的输入转化为整数型,如果出现非数字字符则输出“请输入数字”的提示;如果结果超出范围则输出“请输入数字N(1<=N<=1000000)”;如果没有错误则开始创建数组,转向第二部分。

2.创建数独部分

按照上面的思路编写代码。我的学号后两位是“93”,因此按照题目要求,数独第一位数字是“4”。我的想法是,首先将首位确定为4,接下来第二位随机生成一个不是4的数字,剩余七位通过循环进行深度优先遍历,以这种方式生成第一个数列。例如,生成的随机数为7时,第一个生成的数列为:

  • 4,7,1,2,3,5,6,8,9

接下来就按照上面思路讲述的方法构造数独。这里我采用比较笨的方法重复写(懒得想更简便的方法了XD):

for (i = 0; i < 3; i++)
{
	for (j = 0; j < 3; j++)
	{
		sdk[3][3 * i + j] = sdk[0][3 * i + (j + 1) % 3];
		sdk[6][3 * i + j] = sdk[0][3 * i + (j + 2) % 3];
	}
}  //用来构造第一、第四和第七行
for (i = 0; i < 9; i++)
{
	sdk[1][i] = sdk[0][(i + 3) % 9];
	sdk[2][i] = sdk[0][(i + 6) % 9];
	sdk[4][i] = sdk[3][(i + 3) % 9];
	sdk[5][i] = sdk[3][(i + 6) % 9];
	sdk[7][i] = sdk[6][(i + 3) % 9];
	sdk[8][i] = sdk[6][(i + 6) % 9];
}  //用构造好的三行构造其他部分

通过这一过程,我们就根据一个初始序列构造出了一个完整数独。接下来是变换的过程。由前所述,对每个数独都有36种不同的变换。此段代码如下:

for (i = 0; i < 6; i++)
{
	for (j = 0; j < 6; j++)
	{
		if ((i == 1) || (i == 3) || (i == 5)) change(4, 5);
		if ((i == 2) || (i == 4)) change(3, 4);
		if ((j == 1) || (j == 3) || (j == 5)) change(7, 8);
		if ((j == 2) || (j == 4)) change(6, 7);
		//下面用于输出生成的数独并进行计数
		n--;
		for (s = 0; s < 9; s++)
		{
			for (t = 0; t < 8; t++)
			{
				cout << sdk[s][t] << " ";
			}
			cout << sdk[s][8] << endl;
		}
		if (n > 0) cout << endl;
		else return;
	}
}

change函数表示交换参数所示的两行内容。此段用i表示456行的交换过程,用j表示789行的交换过程。这里的原理为:

用A、B、C表示第4、5、6行,i表示第几种变换(j同理)。
i=0时,顺序为ABC;i=1时,顺序为ACB;i=2时,顺序为CAB;
i=3时,顺序为CBA;i=4时,顺序为BCA;i=5时,顺序为BAC;
此为全部六种情况,可见i为1、3、5时,交换二三位的内容;i为2、4时,交换一二位的内容。

通过这种交换,我们得到了全部36种变换。变换结束后再次进入深度优先遍历部分,直到达到数量要求。

3.求解数独部分

由主函数调用solve函数,在solve函数中,九行九行地将数字存入数组,存入数组后开始求解。数独需要保证每行、每列、每个九宫格内都没有重复的数字,因此我写了三个简单的函数judge_row、judge_column和judge_box来进行判断,用judge函数把三个函数结合到一起,三个函数全为真时则返回真,否则返回假。

新建一个布尔型函数fill,用来填0,fill有一个参数m。m表示这是数独中的第m位(1≤m≤81),从0开始到达81后则表示填入成功,返回true。此段代码如下:

bool fill(int m)
{
	int i, x, y;   //x为第m个元素的行数,y为列数
	if (m >= 81) return true;
	x = m / 9;
	y = m % 9;
	if (sdk[x][y] == 0)
	{
		for (i = 1; i <= 9; i++)
		{
			if (judge(x, y, i))
			{
				sdk[x][y] = i;
				return fill(m + 1);
			}
		}
	}
	else return fill(m + 1);
	return false;
}

sdk数组中存储的即是数独数组,x和y分别是第m个元素所在的行数和列数,接下来察看该元素是否为0,如果不是0则递归地查找下一个元素;如果是0则循环地查找里面所能填写的数字,填写好后再递归地查找下一个元素。这是一个简单的DFS过程。

填写成功后回到solve函数,将数组中的元素输出到文件内。全部求解结束之后,在命令行上输出“求解完毕”的字样。

性能优化

因为本人比较懒,所有的函数都写在了一个文件里。再加上我能力有限,只能写出这种程度的代码;时间也有限,无法完成更细致的优化。另外我不太会使用性能测试工具,只能直观地以代码运行时间为判断依据。

通过上面的代码写出的程序运行时速度比较慢,生成一百万个数独需要大概一分半钟左右的时间。通过同学之间的讨论发现极有可能是输出的问题,原始代码是使用C++的cout进行输出的,每产生一个数独就进行一次输出。这样大大减缓了程序的运行速度。

针对此进行改进,创建一个非常巨大的buffer字符型数组,然后将每次产生的数独都写入这个数组,同时数组中也写出空格和换行。所有数独生成结束之后,使用fputs函数进行输出。

令N=10000进行测试,使用clock函数,最后输出运行的时间。优化前需要3359毫秒才能运行完毕,而优化后只需要14毫秒,效果显著。

实验总结

虽然整个程序并不长,实验也相对简单,难度并不大。但是毕竟是自己独立完成,中途还是遇到了很多困难,也积累了很多经验。

没有使用面向对象的方式进行编程,主要还是因为自己对面向对象的内容实在不熟悉,只是知道很多原理性的内容,没有真正实践过。再加上时间有限,只能采用我最熟悉的方式进行编程。所有的函数也写在了同一个文件里,所幸代码不长,还比较清晰。

实验过程中发现了自己的很多不足,甚至对C++还有很多不熟悉的地方,尤其是文件操作和控制台的部分,查阅了很多资料,还好最后能够完成,得到了一个算是自己能够满意的结果吧。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
数独是一种经典的游戏,在计算机科学中也有很多人尝试使用编程语言来编写数独程序。其中,Python是一种常用的编程语言,比较适合用来编写数独程序。 首先,编写数独程序需要定义数独的数据结构。常用的方式是使用二维数组来表示数独的九宫格,每个格子用数字0~9来表示,其中0代表着该格子为空。 其次,需要编写数独求解算法。一般而言,数独问题可以使用回溯算法来解决。具体的实现过程可以在每次填完一个数字后,递归地填下一个位置。如果填到了最后一个位置,就代表求解成功;如果某个位置填不进任何数字了,就需要回溯到上一个位置重新填。这个过程需要进行一定的剪枝,以提高求解的效率。 除了求解算法之外,还需要编写数独的生成算法。一般而言,生成数独的过程是从一个空的九宫格开始,并随机填入一些数字,然后用求解算法验证这个数独是否有解。如果有解,就再填入一些数字,直到生成一个完整的数独。 最后,需要编写用户界面。这可以使用Python提供的GUI库来实现。用户界面可以包括一个可交互的数独棋盘,用户可以通过鼠标或者键盘来填写数字,求解数独或者生成新的数独。为了提高用户体验,还可以添加一些提示和错误检查等功能。 总之,Python编写数独程序是一项比较有挑战性的任务,需要对Python编程语言和数独算法有一定的了解。不过,一旦掌握了这些基本技能,编写高效而优美的数独程序就不在话下了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值