软件工程第二次作业

0.相关链接

题目链接地址
github链接地址

1.解题思路

  • 胡思乱想
    • 大致思路有两条,一个是先生成数独终盘,再矩阵转换,(感觉会比较有限,不考虑),二个是随机填数,可以按照数字填(将1填完,再填2这样),按行/列填,按九宫格填
  • 难度瓶颈
    • 考虑随机填数的思路,难点就是填数要随机,但是同行,同列,同格都有限制,自由又很不自由,一开始打算按照数字填,因为觉得数独,1只跟1有冲突,与其他数字,不在一个位置就好了。但是这样满盘随机放数字,就不知道怎么代码实现。
  • 最终选择
    • 网上看了随机的思路,最后选择按行填数,这里参考silenpy的数独终盘生成算法(java)很暴力,但是很容易实现。相同点:第一行数字无限制,1-9随机放,第二行到第九行,一个一个数字填,每次用SET存1-9,然后将同行,同列,同格数字踢出去,(可能将1-9全部踢出去了)剩余数字随机选择。这样保证当前填数正确。不同点:对无数可选情况的处理,我考虑整行重新填数,但是存在整行数字无论怎么填,都不可能正确的情况。解决方法:设置一个整行重新生成的最大限值,超过此值,整个数独重新生成。
  • 改进版本
    • 最后测试的时候,发现上个思路先判断再放数字太慢,SET会重复插入,删除,而且随机选数必须迭代。改进是先放数字,再判断是否正确,这里参考Swing数独游戏(二):终盘生成之随机法,PS:已经确定一位同学吐槽这个算法,一位同学貌似是吐槽这个算法,但是算法是真的,思路与算法有出入,我也被坑了。

2.设计实现

只是生成数独终盘,不考虑附加作业,就没有考虑类,只是函数。

  • 版本一:
    getShudu首行调用getfirst,剩余72个空格调用getone,最后调用print输出

  • 版本二:
    getshudu首行调用creatArray,首行数字直接等于随机生成的数组数字,第二行到第九行,依旧调用creatArray,但是不可直接填入,checkbtnArray用于判断随机生成一组数字是否可用,for循环从0到8,当前行一个一个填数,checkone判断当前位置能否从数组中取得合法数字,如果有一个位置无法取得合法数字,则此随机数组不可用。需要再次随机生成,最后调用print打印。

3.代码说明

  • 版本一:

    • 对首个数字处理,只需要在getfirst先将7从集合移除,放入第一个数字置,剩余数字再随机
    Array[0][0] = 7;
    basic.erase(7);//basic为集合名称
    
    • getone踢出同行/列/格数字后,如果集合中仍有数字,随机选择一个,如果没有数字,看当前getone调用次数times是否超过最大限制,如果没有,此行重新生成,如果超过,不做处理,getshudu会进行处理
    if (basic.size() == 0) {//无数可选
    	
    		if(times <= MAX_Time){
    			if(col/3==0){
    				for(int k=0;k<3;k++){
    					name[0].insert(Array[col][k]);
    				}
    			}
    			for (int k = 0; k <= row; k++) {
    				getone(col, k);//此行重新生成
    			}
    		}
    	}else {
    		int num = rand() % basic.size();
    		set<int>::iterator it;//定义前向迭代器?
    		int j;
    
    		for (it = basic.begin(), j = 0; it != basic.end(); it++, j++) {
    			if (j == num) {
    				Array[col][row] = *it;
    				//basic.erase(Array[0][i]);
    				break;
    			}
    		}
    
    	}
    
  • 版本二:

    • 随机生成数组,对首个数字的处理,数组btnArray,初始化放入1-9,将1与7交换,然后循环产生随机数字(1-8),将该位置数字与btnArray[1]交换,得到一组随机数字
    void creatArray() {
        times++;
        for (int i = 0; i<9; i++) {
            btnArray[i] = i + 1;
        }
        btnArray[0] = 7;
        btnArray[6] = 1;
        for (int i = 0; i<20; i++) {
            int t = rand() % 8 + 1;
            int temp = btnArray[1];
            btnArray[1] = btnArray[t];
            btnArray[t] = temp;
        }
    }
    
    • checkbtnArray
     bool checkbtnArray(int row){
    	int judge;
    	for(int i = 0 ;i < 9;i++){
    		judge = 0;
    		for(int j = 0;j < 9;j++){
    			Array[row][i]= btnArray[j];
    			if(checkone(row,i)){//如果数字合适,填入,换下个位置
    				judge = 1;
    				break;
    			}
    		}
            //数组数字选遍,仍无合适数字,则此数组不可用
    		if(judge == 0){
    			for(int k = 0;k<=i;k++){
    				Array[row][k]=0;
    			}
    		    return false;
    	    }
    					
        } 
        return true;
    }
    

4.测试运行

  • 命令行输入:

  • 生成文件部分截图

5.效能分析以及改进

仅针对版本二,版本一无力回天了。

  • 第一次改进:100万当时运行了15分钟,发现约50%时间耗费在输出上,(忘记截图,后续补上)原本输出函数
char* path = "./sudoku.txt";	// 创建文件的相对路径
ofstream fout(path);
void print(){
	for (int i = 0; i < 9; i++) {
		for (int j = 0; j < 9; j++) {
			fout << Array[i][j] << " ";
		}
		fout << endl;
	}
	fout << endl;
}

看了大佬代码,用的putchar,果断换,也用printf试过,后者慢一些

freopen("./sudoku.txt", "w", stdout); 
void print() {
	for (int i = 0; i < 9; i++) {
		for (int j = 0; j < 9; j++) {
			char num = '0'+Array[i][j];
			putchar(num);putchar(' '); 
		}
		puts("");
	}
	puts("");
}

运行截图,100万数据:七分多钟

printf函数所占比例:

  • 第二次改进,一次运行后,print函数消耗时间大大减少,但是checkone,与checkbtnArray函数比重增大。
    查看代码发现:数组里面的数字如果合适,会被填入,但是下次依旧会再次被检验。
for(int j = 0;j < 9;j++){
	Array[col][i]= btnArray[j];
	if(checkone(col,i)){
		judge = 1;
		break;
	}
}

将已经正确填入的数字置0,加以判断

for (int j = 0; j < 9; j++) {
	if(btnArray[j]!=0){
		Array[col][i] = btnArray[j];
		if (checkone(col, i)) {
			judge = 1;
			btnArray[j] = 0;//表示已经使用过 
			break;
		}
	} 	
}

这样也保证同行不会有重复,checkone注释掉同行检查

/*
	//同行不可重复 
	for (int i = 0; i < row; i++) {
		if (Array[col][row] == Array[col][i])return false;
	}
	*/

	//同格不可重复 
	for (int i = (col / 3) * 3; i < col; i++) {
		for (int j = (row / 3) * 3; j < (row / 3) * 3 + 3; j++) {
			if (Array[col][row] == Array[i][j])return false;
		}
	}

	//同列不可重复 
	for (int i = 0; i < (col / 3) * 3; i++) {
		if (Array[col][row] == Array[i][row])return false;
	}

运行截图,100万数据,跑了六分多,快了一分多,checkone与checkbtnArray占比有所下降,但是依旧很大,其中消耗最大的函数是checkone

  • 意外发现,本来构建之法上面写了,使用效能分析工具确保编译的程序是Release版本,但是当时不知道release是什么,后面发现Debug可以切换成Release,又跑了一次100万,三分多,不去想大佬程序耗时,就知足了。

6.PSP

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 10 20
· Estimate · 估计这个任务需要多少时间 10 20
Development 开发 630 960
· Analysis · 需求分析 (包括学习新技术) 150 200
· Design Spec · 生成设计文档 10 0
· Design Review · 设计复审 (和同事审核设计文档) 30 10
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 20 10
· Design · 具体设计 120 120
· Coding · 具体编码 180 240
· Code Review · 代码复审 60 300
· Test · 测试(自我测试,修改代码,提交修改) 60 80
Reporting 报告 40 70
· Test Report · 测试报告 20 60
· Size Measurement · 计算工作量 20 10
· Postmortem & Process Improvement Plan · 事后总结,并提出过程改进计划 120 180
合计 680 1050
原文地址:https://www.cnblogs.com/liu424/p/7502217.html