• 八叶一刀流·二之型·疾风数独


    项目相关要求

    Github 项目地址:https://github.com/zjwml4581792/Software-engineering


    第一次的真软工实践作业就是这种题目,想想还有点小激动,终于又要敲代码啦。看着自己现在的编程的“纸飞机阶段”,想完成,还是等待~并心怀希望吧!


    先看题目。

    我是题目

    度娘说:数独(百度百科)

    数独(Sudoku,Crosswords)是源自18世纪瑞士的一种数学游戏。是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复。
    数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。

    image


    工具清单

    • 编程语言: C/C++
    • 编程IDE:Visual Studio 2017 社区版
    • 效能分析工具:Visual Studio Profiling Tools
    • 源代码管理平台:Github
    • markdown 编辑器:有道云笔记

    解题思路

    遇到的困难及解决方法

    • 困难描述

    看到的第一眼就是,这个题目跟程序语言综合设计的那个马踏棋盘和N皇后问题好像啊,一百度,果然三个题目经常绑定出现,用的很多都是回溯。

    然而自己并不是很懂回溯,于是自己找了找不是回溯算法的大神写的博客。可惜大神写的算法我基本看不懂。

    各种各样的问题显示了自己的功力不足,函数什么时候开始回溯啊,调用函数自己的那行到底写在哪里啊,比较好的想法到底是哪一种啊……

    • 做过哪些尝试

    自己什么都不懂,直接开始写是不现实的,于是开始问学长。

    先问了个直系老乡研究生谷歌(还是微软)大佬,说“数独问题用 dancing links 算法来写”,,一百度好像dancing links 真的很厉害的样子,但是好像这么复杂的双向链表我真的写不出来。

    自己百度“数独生成算法”吧,看看前辈们都用的是什么算法,用的思想是不是太超前自己能不能接受。

    找到的比较看得懂自己印象比较深有启发的:

    1. 【算法研究】数独高效完全解生成算法的研究和实现
    2. JavaScript九宫格数独生成算法
    3. 五大常用算法之四:回溯法

    于是自己尝试了生成 9 个随机数组一行一行填进矩阵,一个一个数字地填,一个九宫填一个九宫地填等等各种方法。

    • 是否解决

    最后采用了 0~9 轮流逐行地填的方法,即从1开始填,从第0行填到第8行,1填完了填2,直到9。

    事实证明采用了某种方法如果理论没问题一定要坚持到底,如果不断地尝试别的方法真的没有效率,就跟《构建之法》里的画扇面一样,过早想着优化,都还没完成必要功能和需求就不要考虑太多。先写出来了再说。

    • 有何收获

    【1】 中汲取了“按九宫格从左到右从上到下,从 1-9 轮流填,填完了再填下一个数”的思想并改造;

    【2】 中得到了先把无依赖的主对角线上的三个小九宫填满,再填其他九宫的思想(但是最终并没有用到这方法

    【3】 中了解了怎么写回溯算法。

    自己的搜索能力暴涨,code能力小幅升,熬夜能力MAX,泡面技巧infinite
    拖延症已病入膏肓

    关键代码

    习惯看到题目就先创建个类,这个数独类至少需要有构造函数、析构函数、创建矩阵、打印矩阵四个函数,数独矩阵二维数组,然后根据需要添加了初始化函数,判断函数,文件变量等,详见代码注释。

    class Sudo
    {
    public:
    	Sudo(int id);               //构造函数
    	~Sudo();                    //析构函数
    	void createSudoku(int id);//创建数独矩阵
    	void print();	            //打印
    	void init();	            //初始化
    
    private:
    	ofstream outfile;	            //输出文件
    	int arr[9][9];				//数独矩阵数组
    	int flag;			    //是否把每一行的该数都填好的标志
    	int firstNumber;		            //题目要求的跟学号有关的数
    	bool isRight(int row, int column, int num);//判断row行column列是否可以填num
    	vector<int> randVector();		//生成随机数组
    	void fillNumber(int num, int row);	//从row开始每一行开始填充num
    	void clean(int i);                  //填充失败,把i数清除,重新填过
    };
    

    看到题目想到要写的第一个函数就是判断函数,在写的时候先分别写了行判断、列判断、九宫判断三个函数,以及调用三个函数的isRight原始版,后来整合到一起了就成了下面的新生版。由于是逐行填充的,只需要检查列和九宫符不符合要求即可。

    后来思考如果按照每个九宫来填数,只需要判断行和列会不会更快,但是按九宫来填数,填数就麻烦了,根据第一个博客里的来看,应该按九宫来会快些。

    bool Sudo::isRight(int row, int column, int num)
    {//判断所在位置是否可行
    	for (int i = 0; i < 9; i++)
    	{//检查列可不可以 
    		if (arr[i][column] == num)
    			return false;
    	}
    	int northwestX = row / 3 * 3;//算小九宫的第一个位置
    	int northwestY = column / 3 * 3;
    	//检查九宫 
    	for (int i = 0; i < 3; i++)//横向检查
    	{							
    		for (int j = 0; j < 3; j++)//竖向检查
    		{						
    			if (arr[i + northwestX][j + northwestY] == num)
    				return false;
    		}
    	}
    	return true;
    }
    

    如果是普通的随机数的生成的话,一定会生成重复的随机数,于是生成一个0-8的随机数组,需要用的时候遍历数组。
    如何生成随机数组的方法是网上找的,C++下数组随机shuffle的方法解

    vector<int> Sudo::randVector()//生成随机数组
    {
    	vector<int> result;
    	result.clear();
    	for (int i = 0; i < 9; i++)
    		result.push_back(i);
    
    	random_shuffle(result.begin(), result.end());
    	return result;
    }
    

    创建数独,先填要求的数,再从1-9填,没法填了就退回上一个数重新填过。

    void Sudo::createSudoku(int hhhhhh)//创建数独
    {
    	fillNumber(firstNumber, 1);	//先把要求的那个数填进去
    	for (int i = 1; i < 9; i++)	//从1到9开始填充
    	{
    		if (i == firstNumber)//如果是要求的那个数
    			continue;	//则跳过
    
    		fillNumber(i, 0);   //逐行填充数i
    
    		if (flag)	    //如果填充好了
    			flag = false;   //将flag置false,准备下一个数
    		else		//如果没填好
    		{
    			clean(i);//把上一个数清掉,重填
    			i -= 2;		//先-2,待会+1,从上一个数重新开始
    		}
    	}
    
    	for (int i = 0; i < 9; i++)//发现上面填完了之后9是空的
    	{
    		for (int j = 0; j < 9; j++)//就写一个循环把9补上
    		{
    			if (arr[i][j] == 0)
    				arr[i][j] = 9;
    		}
    	}
    }
    

    最重要的填数,不过这个回溯挺最简单,从第0行开始填num。

    void Sudo::fillNumber(int num, int row)//从row行开始填充num
    {
    	if (row > 8)		//如果0-8行都填完了
    	{
    		flag = true;	//将填好的标志flag置true
    		return;		//返回
    	}
    
    	vector<int>temp = randVector();	//生成随机数组,
    	for (int i = 0; i < 9; i++)	//从数组第一个数开始测试第row行temp[i]列行不行
    	{
    		if (arr[row][temp.at(i)] != 0)//如果这个位置填过了
    			continue;	//跳过这列
    
    		if (isRight(row, temp.at(i), num))//如果这个位置可以
    		{
    			arr[row][temp.at(i)] = num;	//填上去
    			fillNumber(num, row + 1);	//填下一个行
    			if (flag)	        //如果填好了
    				return;	        //返回
    			arr[row][temp.at(i)] = 0;//如果这个数这行填不成,把上一行该数置0,换temp下一个数试试
    		}
    	}
    }
    

    程序运行截图

    图中的运行时间是测试用的,暴露了自己这个算法有待改进,很大的改进。

    运行截图

    效能分析

    摘要

    摘要

    调用关系树

    调用关系树

    从中可以看出,主要是填充数函数占了最多时间,因此算法本身有很大的改进空间,比如判断函数,从周同学的作业中,可以看出自己和TA的差距;也可以把fillNumber函数的返回值设为Bool,也能少个变量flag。另外vector系列也是比较耗时的,用的不多的话,自己写个随机生成数组的函数,借助库函数是比较耗时。

    关于执行力、泛泛而谈的理解

    百毒百科说

    执行力是指有效利用资源、保质保量达成目标的能力,指的是贯彻战略意图,完成预定目标的操作能力。是把企业战略、规划、目标转化成为效益、成果的关键。
    执行力包含完成任务的意愿,完成任务的能力,完成任务的程度。对个人而言执行力就是办事能力;对团队而言执行力就是战斗力;对企业而言执行力就是经营能力。
    简单来说就是行动力。

    我的理解就是,在拿到任务之后,用自己最专注的热情去完成这件事,最及时地完成,对待任务当机立断、雷厉风行,以秋风扫落叶之势,脚踏实地、贯彻始终,知行合一。

    其实上面的这段话就是泛泛而谈,没有什么针对性,就比如创新创业比赛上,上台的很多学生都是侃侃而谈,对于自己的产品本身的介绍并不多,就算是客观的场面话,也没有讲到针对于自身产品最大的痛点。


    PSP

    PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
    Planning 计划 20 10
    · Estimate · 估计这个任务需要多少时间 20 10
    Development 开发 1775 1820
    · Analysis · 需求分析 (包括学习新技术) 60 73
    · Design Spec · 生成设计文档 30 没有做
    · Design Review · 设计复审 (和同事审核设计文档) 30 单干,不需要
    · Coding Standard · 代码规范 (为目前的开发制定合适的规范) 15 7
    · Design · 具体设计 120 120
    · Coding · 具体编码 1440 1560
    · Code Review · 代码复审 60 20
    · Test · 测试(自我测试,修改代码,提交修改) 20 40
    Reporting 报告 155 78
    · Test Report · 测试报告 30 30
    · Size Measurement · 计算工作量 15 15
    · Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 120 33
    合计 1960 1908

    学习进度条

    第 N 周 新增代码(行) 累计代码(行) 学习耗时(小时) 累计学习耗时(小时) 重要成长
    第 0 周 192 192 31 31 复习C++语法、学习VS2017操作、了解回溯

    参考资料

  • 相关阅读:
    有关css的效果展示
    php将代码上传至服务器步骤
    php连接数据库
    PHP如何从一个页面跳转到另一个页面
    Windows计算器
    查询本地正在运行的端口号
    wamp虚拟路径访问配置
    sublime text3 Emmet (原zenCoding)安装方法
    Latex(一)公式自动编号与自动引用
    Linux下Django开发学习(一)
  • 原文地址:https://www.cnblogs.com/SoShun/p/7500037.html
Copyright © 2020-2023  润新知