《高级软件工程》第二次作业

个人项目实战

GitHub地址
GitHub地址:附加题1

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

(1)解题思路

  • 刚刚拿到的关于数独的题目的时候,首先需要了解数独的棋盘的基本布局以及数独的玩法。通过上网查资料,在网上找到数独棋盘的基本位置,以及摆放数字的需要注意的点。
  • 然后,我认为可以像在网上或者书上做数独那样,先给出一个空白的棋盘,随机填入几个数字,即让程序给出一个所谓的"数独"的棋盘,之后,通过不断的填空,直到填满所有的空格。
  • 最初想法是:
  • 先生成一系列的随机的行和列,以及一系列在0-9之间的随机的值,对一个9*9的全部初始化为0的vector进行填充,用来获取一个与平时做数独题目时看到的棋盘的格局。
  • 之后,对其进行找到vector中为非0的部分,对其进行试探性的填充。如果不行,就进行回溯。
  • 在设计完之后发现了一个问题,这个程序是否能够得到一个“终盘“,以及程序对于生成的”终盘“的解的快慢,其实,都是依赖于之前所随机生成的类似于终盘形式的“终盘”。这样的程序,在初始生成“终盘”时就无法保证质量,以至于在求解的时候,时间难以估量、是否能最终生成一个”终盘“难以保证。
  • 此时,就必须换一种方法了。既然不能倒着去求解,为什么不尝试一下正着生成呢?此时的大致想法就是从原始的初始化为0的vector,直接一个一个往进填数字,从最初就要往终盘的方向靠。
  1. 初始化vector为0,先给出一个set,装入待填数字,即1-9,随后,将vector以及这个set集合传入“生成”的方法中。
  2. 由于此时vector全为0,那么,此时按照从从左到右,从上到下的顺序来进行遍历赋值,这样,一旦发生回溯,也不需要额外的记录位置的辅助存储空间了,这样也就节省了空间。

(2)设计实现

  • 本次实验没有涉及到类,涉及到了几个方法,方法名及其作用,如下:
  1. int main(int argc,char* argv[]) 主函数,程序的入口,同时命令行接收的参数也在这里。
  2. bool shengcheng(vector< vector< int >> &shuju, int hang, int lie, const set< int> &shu) 生成棋盘的函数,通过其不断循环,最终,将vector<vector< int >> shuju中存储的内容变成一个符合终盘的“终盘”。
  3. bool valid(const vector< vector< int > > &shuju, const int hang, const int lie, const int &num) 判断函数,用来判断某个数是否可以放在某个确定的位置
  4. void print(vector < vector< int > > &shuju) 打印函数,将符合条件的vector进行输出,输出到sodoku.txt文件中
  • shengcheng()函数的流程图如下:

(3)代码说明

bool shengcheng_test(vector<vector<int>> &shuju, const set<int> &shu) {
        shuju.at(0).at(0) = (7+7)%9+1;
	int hang(0), lie(1), suiji_number(0);
	vector<set<int>> zancun;
	set<int> houxuan_number(shu);
	while (hang < 9) {
		while (lie < 9) {
                        
			while (!houxuan_number.empty()) {
				suiji_number = Random(9) + 1;
				//cout << "suiji_number " << suiji_number<<endl;
				auto itear = houxuan_number.find(suiji_number);
				if (itear != houxuan_number.end()) {
					//不是end 说明这个数据在set中是存在的
					houxuan_number.erase(itear);
				}
				else
				{
					//随机找到的这个数是不存在在set中的数字
					continue;
				}
				//已经生成了随机数
				if (valid(shuju, hang, lie, suiji_number)) {
					//如果成功了 将shuju的i,j置为当前的数
					shuju.at(hang).at(lie) = suiji_number;
					//之后 移动行、列  准备接收新的东西
					if (hang == 8 && lie == 8) {
						//已经接收完毕
						return true;
					}
					if (lie == 8 && hang < 8)
					{
						lie = 0;
						hang++;

					}
					else
					{
						lie++;

					}
                                        //将本次hang,lie还没有用过的数字的set集合加入到存储的zanshi这个vector里去  以待回溯使用
					zancun.push_back(houxuan_number);
					houxuan_number = shu;
					break;//跳出找随机数的循环
				}
				else
				{
					continue;
				}
			}
			if (houxuan_number.empty()) {
				//如果候选的number为空了  那么 进行回溯
				shuju.at(hang).at(lie) = 0;//将当前位置置零
				if (lie > 0 && lie <= 8) {
					lie--;
				}
				if (lie == 0 && hang>0)
				{
					lie = 8;
					hang--;
				}
				if (lie == 0 && hang == 0) {
					cout << "没有结果" << endl;
					return false;
				}
				auto iend = zancun.end();//拿出上一个hang,lie还没用到的数字的set集合
				houxuan_number = *(iend - 1);
				zancun.pop_back();
			}
		}
	}
}

(4)测试运行

  • 在命令行界面,支持作业样例里面的输入:
6 2 9 3 7 1 8 4 5
4 3 1 8 9 5 7 6 2
5 7 8 6 2 4 3 1 9
9 8 4 2 6 7 5 3 1
7 6 2 1 5 3 9 8 4
1 5 3 4 8 9 6 2 7
8 1 5 9 4 6 2 7 3
2 4 7 5 3 8 1 9 6
3 9 6 7 1 2 4 5 8

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

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

(5)性能改进

  • 既然是性能改进,那就先看一下目前的情况(测试输出10000组数据)。
  • 可以看出,占用cpu最大的是shengcheng()和print()函数。shengcheng()是主要进行生成最终棋盘的函数,print()则是输出的函数。
  • print()函数的优化:在平时编程中,由于习惯写endl来结束换行,但是,隐约记得老师说的endl会关闭输出流,在下次使用的时候再次打开。那么,这就增加了开销,因为测试数据相对较打,而每次的输出,起码需要开关9次。所以,选择使用输出转义字符(\n)进行换行,从而使输出流一直处在打开的状态。
  • 将print()函数改造之后:
  • 由目前的运行可知,print()函数的开销在缩小,函数的运行时间也减少了几百毫秒。
  • 关于shengcheng()函数:
  • 由图可见,主要的消耗在std::vector< std::set< int,std::less< int>,std::allocator< int> >,std::allocator<std::set < int,std::less< int>,std::allocator< int> > > >::emplace_back<std::set< int,std::less< int >,std::allocator< int> > const & >,那么,也就是在每次循环下一个位置时对于本次还没有用完的随机数的set加入到zanshi这个vector中的步骤。由于回溯的时候,为了能减少valid()函数的判断次数,那么,在随机数的生成上需要做保证,所以需要记录每次还没有用到的数字,所以我认为这是必要步骤,至于怎么优化,目前还未想到(不过有一个小想法,可能采用递归,让系统的递归栈去维护这个每次未用完的数字的集合可能效率会较手动维护高)。

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

附加题(1) (Qt实现)

(1)解题思路

  • 此时的要求是:
  • 99棋盘上挖空不少于30个,不多于60个。每个33的小棋盘中挖空不少于2个。
  • 用户可以在界面上通过点击或输入完成数独题目。
  • 用户完成数独题目后可以得到反馈,知道自己的题目是否做对。
  • 针对第一个要求,总共9*9=81个格子,分为9个宫,要求总的挖空数不少于30,不多于60,那么,此时,我认为最符合条件又不会出左的方法就是:
  1. 总的挖空数分摊到每个宫其实是4-6个,只要保证每个宫挖掉的空格是在4-6之间,那么,肯定会满足题目要求。
  2. 挖空的时候,不要一个一个i,j进行寻找遍历,而是一次操作一块区域,在一块区域后就去下个区域。
  • 第二个要求,作为GUI界面程序所要有的基本功能。我设计为让用户输入而不是点击。
  • 第三个要求,对用户是否答对进行判断。
  1. 因为我目前判断不了自己生成的挖空的数独棋盘是否具有唯一解,所以,不采取比对的方式,而是继续采用判断(行、列、宫)。
  2. 如果再延续之前生成时那样的一个一个判定,可能会浪费时间。所以,在挖空的时候,就对挖空的元素位置进行记录,在实现判断用户是否答对时就只需要对挖空的的位置的元素进行判断。

(2)设计实现

  • 想要实现GUI界面的数独,必然是需要先生成已经进行挖空的数独终盘的棋盘,那么,就需要一个产生这些数据的类(shudu类)。
  • 使用GUI,那么,需要包含一个mainwindow的类
  • 还需要一个检测是否输入正确的类(jiance类)。
  • 主要流程如下:
  1. 在shudu类中实现生成数据,之后,给外界提供可以返回棋盘数据的方法以及挖空位置的方法。
  2. 在mainwindow类中,包含一个table的widget,用来展示数据。
  3. 在jiance类中,对传过来的table的数据以及挖空的位置数据进行处理,给外界提供一个返回是否检测通过的方法。

(3)代码说明

void gongge_wakong(int i, int j) {
    //只要让每个宫里面的空 大于等于4 小于等于6  那么 总数就是大于30 小于60 的
    int wakong_shu = Random(3) + 4;//产生4+(0,1,2)的随机数
    qDebug() << "wakong_shu  " << wakong_shu << endl;
    int jilu (0),suiji_i(0),suiji_j(0);

    while (jilu <= wakong_shu) {
        suiji_i = Random(3) + i;//产生i+(0,1,2)的随机数
        suiji_j = Random(3) + j;
        jilu_QSet = qMakePair(suiji_i, suiji_j);

        if (jilu == 0) {
            s.insert(jilu_QSet);
            jilu++;
        }
        else
        {
            if (s.find(jilu_QSet) == s.end())
            {
                //if not find then insert
                s.insert(jilu_QSet);
                qDebug() << "jilu_QSet  " << jilu_QSet.first << "   " << jilu_QSet.second << endl;
                shuju[suiji_i][suiji_j] = 0;
                jilu++;
            }
            else
            {
                continue;
            }
        }


    }

}

(4)测试运行




总结

  • 此次试验让我做了挺多的新的事情,第一次上传github(以前都是下载被人的),第一次使用性能分析工具来分析性能等,也让我开始注意性能优化这个事情。
  • 想了很久,还是不能想到怎么去判断生成的挖空的数独棋盘是否具有唯一解,所以附加题2还没有完成。
  • 在设计程序之初,总想找到一个不那么暴力的方法去构造出一个棋盘,事实证明,还是没想出来,很遗憾。

posted on 2017-10-08 23:02  简单_J  阅读(147)  评论(0编辑  收藏  举报

导航