zoukankan      html  css  js  c++  java
  • 2019软件工程实践——第三次作业

    GitHub地址

    https://github.com/wujunjie1008/031702537.git

    P2P表格

    PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
    Planning 计划 30min 15min
    Estimate 估计这个任务需要多少时间 24h 26h
    Development 开发 5h 2h
    Analysis 需求分析 (包括学习新技术) 2h 2h
    Design Spec 生成设计文档 15min 30min
    Design Review 设计复审 1h 30min
    Coding Standard 代码规范 (为目前的开发制定合适的规范) 10min 15min
    Design 具体设计 1h 2h
    Coding 具体编码 4h 6h
    Code Review 代码复审 2h 4h
    Test 测试(自我测试,修改代码,提交修改) 3h 4h
    Reporting 报告 3h 3h
    Test Repor 测试报告 30min 1h
    Size Measurement 计算工作量 15min 15min
    Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 30min 15min
    总计 23.16h 26h

    需求

    实现一个多阶数独解题工具。

    数独

    数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。

    初期准备——磨刀不误砍柴工

    由于这次代码的要求是要cmd命令传入参数并且需要文件输入输出的格式,因此我就去百度,没想到竟然顺便解决了我之前一直有的疑问

    int main(int   argc, char*   argv[])
    

    原来main()函数里的第一个参数argc正是对应用cmd命令输入参数的个数,而第二个参数argv[]对应的则是这些参数的字符串形式
    参考了规定的cmd输入后,我写出了一下导入参数的代码

    	char* i;
    	char* o;
    	int m, n, h, l;
    
    	m = atoi(argv[2]);
    	n = atoi(argv[4]);
    	i = argv[6];
    	ifstream fin(i);
    	if (!fin.is_open())
    	{
    		cout << "输入文件不存在";
    		return 0;
    	}
    	o = argv[8];
    	ofstream fout(o);
    

    文件输入:

    		h = 0;
    		while (!fin.eof()) {			//文件输入
    			getline(fin, list);
    			if (list.length() != 2 * m && list.length() != 2 * m - 1) {		//表格输入不规范,与阶数m不相符则退出
    				cout << "表格大小不符合";
    				return 0;
    			}
    			for (l = 0; l < m; l++) {
    				num_list[h][l] = list[l * 2] - 48;
    				if (num_list[h][l] < 0 || num_list[h][l]>9) {		//表格输入不规范,出现非数字字符则退出
    					cout << "九宫格中出现不是0-9的数字";
    					return 0;
    				}
    			}
    			h++;
    

    文件输出:

    		for (h = 0; h < m; h++) {			//文件输出
    			for (l = 0; l < m; l++) {
    				fout << num_list[h][l];
    				if (l != m - 1)
    					fout << ' ';
    				else
    					fout << '
    ';
    			}
    		}
    		fout << '
    ';
    

    解题思路——山重水复疑无路

    对于我个人来说,如果让我来做数独,我比较喜欢用唯余法,及通过该格子的同一行,同一列,同一宫的数来推断出该空格可以填什么数字,而对于难度较低的数独来说,一定会有一个空格只能填一个数字,当这个数字被填入后,就会出现另一个只能填一个数字的格子,因此,我觉得可以将其应用到低阶还有难度较低的9宫格数独中。具体代码如下:

    int weiyu(int m) {
    	cout << "使用唯余法" << endl;
    	int h, l;
    	int may_count;
    	int count = 0;
    	for (h = 0; h < m; h++) {
    		for (l = 0; l < m; l++) {
    			if (num_list[h][l] == 0)
    				count++;
    		}
    	}
    	for (h = 0; h < m; h++) {
    		for (l = 0; l < m; l++) {
    			if (num_list[h][l] == 0) {
    				int may_num[10] = { 0 };
    				may_count = m;
    				for (int bh = 0; bh < m; bh++) {		//遍历行
    					if (num_list[bh][l] != 0) {
    						if (may_num[num_list[bh][l]] == 0)
    							may_count--;
    						may_num[num_list[bh][l]] = 1;
    					}
    				}
    				for (int bl = 0; bl < m; bl++) {		//遍历列
    					if (num_list[h][bl] != 0) {
    						if (may_num[num_list[h][bl]] == 0)
    							may_count--;
    						may_num[num_list[h][bl]] = 1;
    					}
    				}
    				if (m == 4 || m == 6 || m == 8 || m == 9) {				//遍历宫格
    					int max_h = 0, max_l = 0;
    					int gong_h = 0, gong_l = 0;
    
    					//定位当前格在属于第几宫格
    					if (m == 4 || m == 8 || m == 9) {
    						gong_l = (int)sqrt(m);
    						gong_h = (int)(m / gong_l);
    					}
    					else if (m == 6) {
    						gong_h = (int)sqrt(m);
    						gong_l = (int)(m / gong_h);
    					}
    					for (int i = 1; i < m; i++) {
    						max_h = i * gong_h;
    						if (max_h > h) {
    							break;
    						}
    					}
    					for (int i = 1; i < m; i++) {
    						max_l = i * gong_l;
    						if (max_l > l) {
    							break;
    						}
    					}
    
    					//开始遍历
    					for (int i = max_h - gong_h; i < max_h; i++) {
    						for (int j = max_l - gong_l; j < max_l; j++) {
    							if (i > 9)		//消除vs编译器的警告,删去不影响代码
    								i = 9;
    							if (i < 0)
    								i = 0;
    							if (j > 9)
    								j = 9;
    							if (j < 0)
    								j = 0;
    							if (num_list[i][j] != 0) {
    								if (may_num[num_list[i][j]] == 0)
    									may_count--;
    								may_num[num_list[i][j]] = 1;
    							}
    						}
    
    					}
    				}
    				if (may_count == 1) {					//填写数字
    					for (int i = 1; i <= m; i++) {
    						if (may_num[i] == 0) {
    							num_list[h][l] = i;
    							h = 0;
    							l = -1;
    							count--;				//填写成功,未填数减一
    							if (count == 0) {
    								printf("成功
    ");
    								return 0;		//完成数独,返回0
    							}
    							break;
    						}
    					}
    				}
    			}
    		}
    	}
    	return 1;		//未完成数独,返回1
    }
    

    本以为已经大功告成的我却因为输入一道更高难度的数独题目之后,心情又跌落到了谷底。(附上原题)
    0 0 8 0 9 0 0 0 0
    0 7 0 0 0 0 2 8 0
    0 6 4 1 0 0 3 0 9
    0 0 0 8 0 5 9 0 0
    5 0 0 0 0 0 0 0 1
    0 0 9 3 0 4 0 0 0
    8 0 2 0 0 7 5 6 0
    0 9 7 0 0 0 0 1 0
    0 0 0 0 6 0 7 0 0
    唯余法是可以用,但是一旦出现解不唯一,或者没有格子是填唯一一个数的时候唯余法根本无法解出答案。仅仅只是做出简单数独,那和咸鱼有什么区别,于是我又开始寻找新的方法。

    解题思路——柳暗花明又一村

    自闭是想新方法时候的主旋律,脑子在想的是总不能一格一格的试过去吧。后来无意间听到周围的人都在说DFS(深度优先算法),当时还很困惑,DFS和数独有什么关系。再后来发现DFS不正是把每一种可能都试过去嘛。但是我对于DFS的运行时间还是有着一丝担忧。抱着试试看的心态,我开始写DFS的函数。
    ——首先是检查函数,我在原来唯余法的基础上进行改变,就得出现在的check()函数:

    int check(int h, int l, int m) {
    	int num[10] = { 0 };
    	for (int bh = 0; bh < m; bh++) {		//遍历行,寻找重复的数字
    		if (num_list[bh][l] != 0) {
    			num[num_list[bh][l]]++;
    			if (num[num_list[bh][l]] > 1)
    				return 0;
    		}
    	}
    	for (int i = 0; i < 10; i++) {
    		num[i] = 0;
    	}
    	for (int bl = 0; bl < m; bl++) {		//遍历列,寻找重复的数字
    		if (num_list[h][bl] != 0) {
    			num[num_list[h][bl]]++;
    			if (num[num_list[h][bl]] > 1)
    				return 0;
    		}
    	}
    	for (int i = 0; i < 10; i++) {
    		num[i] = 0;
    	}
    	if (m == 4 || m == 8 || m == 9 || m == 6) {			//检验九宫格,寻找重复的数字
    		
    		//定位该单元格所属的宫格
    		int max_h = 0, max_l = 0;					
    		int gong_h = 0, gong_l = 0;
    		if (m == 4 || m == 8 || m == 9) {
    			gong_l = (int)sqrt(m);
    			gong_h = (int)(m / gong_l);
    		}
    		else if (m == 6) {
    			gong_h = (int)sqrt(m);
    			gong_l = (int)(m / gong_h);
    		}
    		for (int i = 1; i < m; i++) {
    			max_h = i * gong_h;
    			if (max_h > h) {
    				break;
    			}
    		}
    		for (int i = 1; i < m; i++) {
    			max_l = i * gong_l;
    			if (max_l > l) {
    				break;
    			}
    		}
    		
    		//正式开始遍历
    		for (int i = max_h - gong_h; i < max_h; i++) {
    			for (int j = max_l - gong_l; j < max_l; j++) {
    				if (num_list[i][j] != 0) {
    					num[num_list[i][j]]++;
    					if (num[num_list[i][j]] > 1)
    						return 0;		//有重复,返回0
    				}
    			}
    		}
    	}
    
    	return 1;		//无重复,返回1
    }
    

    ——接着就是我们的主角DFS(深度优先搜索)函数了:

    int DFS(int n, int m){
    	if (n > (m*m)) {
    		return 1;
    	}
    	if (num_list[n / m][n % m] != 0){		// 不需要填数字,则跳过
    		if (DFS(n + 1, m) == 1)
    			return 1;
    	}
    	else{
    		for (int i = 1; i <= m; i++){		//试填1-9的数字 
    			num_list[n / m][n % m] = i;
    			if (check(n / m, n % m, m) == 1){
    				if (DFS(n + 1, m) == 1)
    					return 1;				//返回时如果构造成功,则返回1
    				else
    					num_list[n / m][n % m] = 0;
    			}
    			else
    				num_list[n / m][n % m] = 0;
    		}
    	}
    	return 0;
    }
    

    没想到看似复杂的思想在用代码实现后可以这么短,最后的结果就是成功的,更高难度的数独也被我拿下了。
    3 5 8 7 9 2 1 4 6
    9 7 1 4 3 6 2 8 5
    2 6 4 1 5 8 3 7 9
    7 2 6 8 1 5 9 3 4
    5 4 3 6 7 9 8 2 1
    1 8 9 3 2 4 6 5 7
    8 1 2 9 4 7 5 6 3
    6 9 7 5 8 3 4 1 2
    4 3 5 2 6 1 7 9 8

    寻求改进

    一方面出于对DFS运行速度的不放心,另一方面不想让我之前写的唯余法函数就这个被删除,我开始用vs自带的性能探查器探查他们运行时间的对比:

    ——首先是单独使用DFS的函数运行时间:

    ——接着是先使用唯余法若无解再使用DFS的函数运行时间:

    可以看出尽管测试问题中,只有三个问题一定需要用到DFS,但是使用唯余法加DFS的策略明显比单纯用dfs要来的快,因此证明唯余法和DFS结合在时间上具有更高的效率。

    单元测试

    void test(int m) {
    	int h, l;
    	int may_num[10] = {0};
    	for (h = 0; h < m; h++) {
    		for (l = 0; l < m; l++) {
    			if (num_list[h][l] == 0) {
    				cout << "失败" << endl;
    				return;
    			}
    			else {
                                    for (int i = 1; i < 10; i++)
    					may_num[i] = 0;
    				for (int bh = 0; bh < m; bh++) {		//遍历行
    					if (num_list[bh][l] != 0) {
    						may_num[num_list[bh][l]] ++;
    						if (may_num[num_list[bh][l]] > 1){
    							cout << "行失败" <<bh<<' '<<l<< endl;
    							return;
    						}
    					}
    				}
    				for (int i = 1; i < 10; i++)
    					may_num[i] = 0;
    				for (int bl = 0; bl < m; bl++) {		//遍历列
    					if (num_list[h][bl] != 0) {
    						may_num[num_list[h][bl]] ++;
    						if (may_num[num_list[h][bl]] > 1) {
    							cout << "列失败" << h << ' ' << bl << endl;
    							return;
    						}
    					}
    				}
    				for (int i = 1; i < 10; i++)
    					may_num[i] = 0;
    				if (m == 4 || m == 6 || m == 8 || m == 9) {				//遍历宫格
    					int max_h = 0, max_l = 0;
    					int gong_h = 0, gong_l = 0;
    
    					//定位当前格在属于第几宫格
    					if (m == 4 || m == 8 || m == 9) {
    						gong_l = (int)sqrt(m);
    						gong_h = (int)(m / gong_l);
    					}
    					else if (m == 6) {
    						gong_h = (int)sqrt(m);
    						gong_l = (int)(m / gong_h);
    					}
    					for (int i = 1; i < m; i++) {
    						max_h = i * gong_h;
    						if (max_h > h) {
    							break;
    						}
    					}
    					for (int i = 1; i < m; i++) {
    						max_l = i * gong_l;
    						if (max_l > l) {
    							break;
    						}
    					}
    
    					//开始遍历
    					for (int i = max_h - gong_h; i < max_h; i++) {
    						for (int j = max_l - gong_l; j < max_l; j++) {
    							if (num_list[i][j] != 0) {
    								may_num[num_list[i][j]] ++;
    								if (may_num[num_list[i][j]] > 1) {
    									cout << "宫失败" << i << ' ' << j << endl;
    										return;
    								}
    							}
    						}
    					}
    				}
    			}
    		}
    	}
    	cout << "成功"<<endl<<endl;
    }
    

    以上是我用来检验DFS的测试函数,当出现错误时,效果如下:

    3阶:

    4阶:

    5阶:

    6阶:

    7阶:

    8阶:

    9阶:


    总结

    通过这次的实践作业,我学会了对于自己的项目,不仅仅要做出来,还需要对其debug,做多次测试,以及优化,就像我最后引入了DFS算法一样,而且我还学会了用VS2017的性能探查器来对代码的各个部分做出检测,来优化我的代码。不仅如此,我还学会应该合理分配我的时间,我是在距离deadline还有4天的时候才开始做的,做起来就像在和时间赛跑,所以还有很多地方做的不足,在之后的作业中这点还是需要注意。

  • 相关阅读:
    转贴: AV音响连接方法 回上个问题,次世代音轨通过HDMI透传给了功放,那图像信号怎么给到电视? 这篇文章教你
    openmosix学习心得,openmosix和pbs系统的差异
    转载:高清音频格式详解
    PVFS2 Failover Policy - 当一块硬盘坏了以后,PVFS如何应对?数据会丢失么?
    转贴: 浅析多声道LPCM TRUE HD DTS HD之异同
    [存储知识]SAN和NAS的进一步解析
    转贴: 次时代音轨
    7×24小时硬盘-正在走向桌面用户的企业级硬盘
    Learning NFS/NIS 2nd 读书笔记-Chapter1 Network Fundamentals
    新闻管理页面源码
  • 原文地址:https://www.cnblogs.com/J-J-1008/p/11586587.html
Copyright © 2011-2022 走看看