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

    Github项目地址:https://github.com/wym990806/031702425

    前言

    第一次编程作业,跟自己以往大一大二所要完成的东西根本就不是一个量级的。在看到这个题目之后,我已经有了一个基本的想法,只是怎么去实现的问题。真正阻止我及时完成这一次作业的反而是一些代码以外的,比如Git的使用、命令行传入参数、性能分析、单元测试等等。这些都是我之前没有接触过的,着实伤脑筋。
    

    1、PSP表格

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

    2、解题思路

    当入手数独这道题目的时候,我就已经决定好自己的初方案了,那就是采取深搜对整个盘面进行递归遍历。若给出的数独盘面有解,则此方法必定可以找出一解,但是该方法是无视其余格的可能性,直接从第一个格子开始操作,这就可能导致最坏情况下,需要从第一个格子递归到最后一个格子9次(第一个格子只能填9的情况下)。
    

    3、几个主要函数

    ①、legal函数(合法性检测)

    数独填入数字的规则是使填入数字在每一行、每一列还有每一宫上仅出现一次。下面给出一张图来更加直观地表示。
    

    设计一个legal函数通过遍历当前格所在行、列、宫已经有的数字来判断填入该数是否合法。
    
    /*legal合法性函数检测输入数字是否符合数独的要求*/
    /*满足输入的数字在所在行以及所在列中仅出现一次*/
    /*若宫格阶数为4689,则还需判断输入数字在所在宫中仅出现一次*/
    /*输入参数分别为行、列、需判断的输入数字*/
    bool legal(int i, int j, int x) {
    	/*行合法性检测*/
    	for (int b = 1; b <= m; b++) {
    		if (x == disksurface[i][b])
    			return false;
    	}
    	/*列合法性检测*/
    	for (int a = 1; a <= m; a++) {
    		if (x == disksurface[a][j])
    			return false;
    	}
    	/*若该阶数存在宫,宫合法性检测*/
    	if (m == 4 || m == 6 || m == 8 || m == 9) {
    		int interval_i, interval_j;
    		int begin_i, begin_j;
    		/*由于阶数的不同,每个宫所占的行列数也不相同*/
    		switch (m)
    		{
    		case 4:
    			interval_i = 2;
    			interval_j = 2;
    			break;
    		case 6:
    			interval_i = 2;
    			interval_j = 3;
    			break;
    		case 8:
    			interval_i = 4;
    			interval_j = 2;
    			break;
    		case 9:
    			interval_i = 3;
    			interval_j = 3;
    			break;
    		}
    		begin_i = ((i - 1) / interval_i)*interval_i + 1;
    		begin_j = ((j - 1) / interval_j)*interval_j + 1;
    		for (int a = begin_i; a < begin_i + interval_i; a++) {
    			for (int b = begin_j; b < begin_j + interval_j; b++) {
    				if (x == disksurface[a][b])
    					return false;
    			}
    		}
    	}
    	return true;
    }
    

    ②DFS深搜算法

    核心算法大概流程由以下流程图展示
    

    算法主要思想:从第一格开始进行深搜,若深搜到最后一格,且当前格已经有数字的话,说明当前数独盘已经填写完毕,有解。若当前格数字为0,说明未填入数字,设置一个x,从1至m(设定阶数)进行合法性判断,合法地继续深搜下一格,若下一格无可填入数字,就当当前格清零,继续判断x的循环。这样循环到最后,若给定数独有解,必定可以给出一解。
    
    /*以深度优先算法计算数独*/
    bool DFS(int a, int b) {
    	while (disksurface[a][b] != 0) {
    		if (a == m && b == m) {
    			return true;
    		}
    		/*对于当前位置的判断,若处于行末,则行加一,列置为一,否则列加一*/
    		else if (b == m) {
    			a++;
    			b = 1;
    		}
    		else {
    			b++;
    		}
    	}
    	for (int x = 1; x <= m; x++) {
    		if (legal(a, b, x)) {
    			disksurface[a][b] = x;
    			if (DFS(a, b)) {
    				return true;
    			}
    			/*若执行到这边,说明下一格无可填入数字,将当前格清0,继续判断X*/
    			disksurface[a][b] = 0;
    		}
    	}
    	return false;
    }
    

    ③程序代码

    /*输入文件名以命令行参数传入。*/
    /*Sudoku.exe -m 9 -n 2 -i input.txt -o output.txt*/
    /*m宫格阶数,n待解答盘面数,i输入文件,o输出文件*/
    
    #include <iostream>
    #include <cstdlib>
    using namespace std;
    
    /*全局变量*/
    int m; /*宫格阶数*/
    int disksurface[10][10];/*宫格盘面,题设最大为九宫格*/
    
    /*若待解盘面不唯一时,需要多次使用disksurface,进行重置操作*/
    void reset() {
    	for (int i = 1; i <= m; i++) {
    		for (int j = 1; j <= m; j++) {
    			disksurface[i][j] = 0; /*盘面上所有数重置为0,即未输入状态*/
    		}
    	}
    }
    
    /*legal合法性函数检测输入数字是否符合数独的要求*/
    /*满足输入的数字在所在行以及所在列中仅出现一次*/
    /*若宫格阶数为4689,则还需判断输入数字在所在宫中仅出现一次*/
    /*输入参数分别为行、列、需判断的输入数字*/
    bool legal(int i, int j, int x) {
    	/*行合法性检测*/
    	for (int b = 1; b <= m; b++) {
    		if (x == disksurface[i][b])
    			return false;
    	}
    	/*列合法性检测*/
    	for (int a = 1; a <= m; a++) {
    		if (x == disksurface[a][j])
    			return false;
    	}
    	/*若该阶数存在宫,宫合法性检测*/
    	if (m == 4 || m == 6 || m == 8 || m == 9) {
    		int interval_i, interval_j;
    		int begin_i, begin_j;
    		switch (m)
    		{
    		case 4:
    			interval_i = 2;
    			interval_j = 2;
    			break;
    		case 6:
    			interval_i = 2;
    			interval_j = 3;
    			break;
    		case 8:
    			interval_i = 4;
    			interval_j = 2;
    			break;
    		case 9:
    			interval_i = 3;
    			interval_j = 3;
    			break;
    		}
    		begin_i = ((i - 1) / interval_i)*interval_i + 1;
    		begin_j = ((j - 1) / interval_j)*interval_j + 1;
    		for (int a = begin_i; a < begin_i + interval_i; a++) {
    			for (int b = begin_j; b < begin_j + interval_j; b++) {
    				if (x == disksurface[a][b])
    					return false;
    			}
    		}
    	}
    	return true;
    }
    
    /*以深度优先算法计算数独*/
    bool DFS(int a, int b) {
    	while (disksurface[a][b] != 0) {
    		if (a == m && b == m) {
    			return true;
    		}
    		else if (b == m) {
    			a++;
    			b = 1;
    		}
    		else {
    			b++;
    		}
    	}
    	for (int x = 1; x <= m; x++) {
    		if (legal(a, b, x)) {
    			disksurface[a][b] = x;
    			if (DFS(a, b)) {
    				return true;
    			}
    			disksurface[a][b] = 0;
    		}
    	}
    	return false;
    }
    
    int main(int argc, char * argv[]) {
    	int n; /*待解答盘面数*/
    	FILE *input;
    	FILE *output;
    	/*由于argv[0]固定为程序名,输入第一个参数为argv[1],以此类推*/
    	m = atoi(argv[2]);
    	n = atoi(argv[4]);
    	/*以只读方式打开input文档*/
    	input = fopen("input.txt", "r");
    	if (input == NULL) {
    		return -1;
    	}
    	output = fopen("output.txt", "w");
    	if (output == NULL) {
    		return -1;
    	}
    	fclose(output);
    	while (n > 0) {
    		reset(); /*执行写入操作前重置盘面*/
    		/*输入盘面*/
    		for (int i = 1; i <= m; i++) {
    			for (int j = 1; j <= m; j++) {
    				/*fscanf()函数,从一个流中输入,遇到空格或者换行时结束*/
                                    /*若使用fscanf会报警告,故更改为fscanf_s*/
    				fscanf_s(input, "%d", &disksurface[i][j]);
    			}
    		}
    		DFS(1, 1);
    		/*以追写方式打开output文档*/
    		output = fopen("output.txt", "a");
    		if (output == NULL) {
    			return -1;
    		}
    		/*将按输入文档格式将盘面数据写入*/
    		for (int i = 1; i <= m; i++) {
    			fprintf(output, "%d", disksurface[i][1]);
    			for (int j = 2; j <= m; j++) {
    				fprintf(output, " ");
    				fprintf(output, "%d", disksurface[i][j]);
    			}
    			fprintf(output, "\n");
    		}
    		fprintf(output, "\n");
    		n--;
    		fclose(output);
    	}
    	fclose(input);
    	return 0;
    }
    
    

    ④测试

    以下给出一些测试用例。其中三-八宫格各5例,九宫格10例。
    


    4、Code Quality Analysis工具分析

    当使用fscanf函数时,vs会报警告
    

        然而使用fscanf_s函数时并不会。
    

    5、程序代码改进

    深搜算法虽然可以给出一解,占用可能会系统过多的资源,且别一次都从第一格开始深搜,不考虑格子的可能性,会造成多余的时间浪费。改进措施:加入一个结构体,记录每一个格子可填入的数字以及合法的数字个数,这样进行深搜是时,从可能性最少的格子开始,每次都对可能性最少的格子继续深搜。这样可以有效减少程序花费的时间。
    具体代码还未实现,存在bug未剔除,故不放出,debug结束后会在此进行修改添加。
    

    5、解决(?)作业后的感想总结

    在看到第一次代码作业前,有设想过会完成得很困难,但真正看到题目、真正开始做之后才更让我感觉到无从下手、举步维艰。PSP表格是什么,其中各项内容代表了什么,有些事项自己以前都没有做过,要怎么开展?Code Quality Analysis工具是什么,在哪使用,该怎么用?性能分析工具Studio Profiling Tools又该怎么用,怎样从中找出自己程序的瓶颈?如何使用Github?怎样编写单元测试代码?如何将文件名以命令行参数传入?等等问题。其中有些在作业完成过程中基本学会了,还有些至今还未学会(作业截止后仍会继续学习)。总而言之,通过这一次作业,深刻理解到软件工程实践与以往代码课的不同。接下来的第一次结队编程作业,为了不拖队友的后腿,我会尽力跟上队友的节奏,学习新的知识,尽可能地利用空闲时间去提升自己。
    

    (还是自己大一大二不争气,不说了,学习使我快乐)

  • 相关阅读:
    c++深拷贝与浅拷贝
    c++构造函数的explicit
    c++虚函数和虚函数表
    c++重载、重写、隐藏(重定义)
    c++传值、传指针、传引用
    ASP.Net Core API 学习の中间件
    WPF中String Format的用法
    ASP.Net Core API 全局处理异常
    989. Add to Array-Form of Integer
    1014. Best Sightseeing Pair
  • 原文地址:https://www.cnblogs.com/yumesinyo/p/11582122.html
Copyright © 2011-2022 走看看