PSP(预估) 见下表
PSP | Personal Software Process Stages | 预估耗时(分钟) |
---|---|---|
Planning | 计划 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 30 |
Development | 开发 | 220 |
· Analysis | · 需求分析 (包括学习新技术) | 60 |
· Design Spec | · 生成设计文档 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 |
· Design | · 具体设计 | 10 |
· Coding | · 具体编码 | 30 |
· Code Review | · 代码复审 | 20 |
· Test | · 测试(自我测试,修改代码,提交修改) | 30 |
Reporting | 报告 | 35 |
· Test Report | · 测试报告 | 10 |
· Size Measurement | · 计算工作量 | 5 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 |
合计 | 285 |
解题思路
大概浏览题目之后,着重看了下项目需求
项目需求
利用程序随机构造出N个已解答的数独棋盘 。
需求简介明了,即构造N个数独棋盘。之前对数独游戏只有大概的了解,所以还是先对“数独”这个概念重新了解下:
数独是源自18世纪瑞士的一种数学游戏。是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复。
数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。
对问题进行抽象,即构造一个9*9的矩阵满足如下要求:
- 矩阵元素只能为1-9的数字
- 每一行的数字不重复
- 每一列的数字不重复
- 9个3*3小宫格里的数字不重复
数字只有1-9,想到可以用暴力搜索来求解数独棋盘。
设计实现
设计一个关键函数递归求解数独棋盘,只要每次检验是否满足上面的四个条件,大致流程如下。
代码说明
以下为关键函数代码:
bool solve_sudoku(int i, int j) //求解第i行,第j列元素可放置的数{ if (i > 9 || j > 9) return true; //行或列已填满,返回true for (int k = 1; k < 10; ++k){ bool Isok = true; if (Isok){ //检查所在行是否可以填k for (int t = 1; t < j; ++t) if (sudoku[i][t] == k){ Isok = false; break; } } if (Isok){ //检查所在列是否可以填k for (int n = 1; n < i; ++n) if (sudoku[n][j] == k){ Isok = false; break; } } if (Isok){ //检查所在小九宫格是否可以填k int low_i = i / 3 * 3 + 1; int low_j = j / 3 * 3 + 1; if (i % 3 == 0) low_i = i / 3; if (j % 3 == 0) low_j = j / 3; for (int x = 0; x < 3; ++x){ if (!Isok) break; for (int y = 0; y < 3; ++y) if (sudoku[low_i+x][low_j+y] == k){ Isok = false; break; } } } if (Isok){ //递归求解下一格 sudoku[i][j] = k; if (j < 9){ if (solve_sudoku(i, j + 1)){ return true; } } else { if (i < 9){ if (solve_sudoku(i + 1, 1)){ return true; } } else{ return true; } } sudoku[i][j] = 0; //无解,置0以免影响下次试解 } } return false; //1-9均试过,无解}
测试运行
程序测试运行结果如下图所示。
改进与优化
下图为生成一个数独棋盘所用的时间。显然,递归调用的花销太大。但是,只生成一个数独棋盘的所花的时间也远远超出了一般递归函数所需要的代价。
生成一个棋盘就花了三分钟,递归时间占用89.55%,将优化目标锁定在递归函数。发现二维数组下标出现表达式可能是导致递归耗时的原因,将源代码的
if (Isok){ //检查所在小九宫格是否可以填k int low_i = i / 3 * 3 + 1; int low_j = j / 3 * 3 + 1; if (i % 3 == 0) low_i = i / 3; if (j % 3 == 0) low_j = j / 3; for (int x = 0; x < 3; ++x){ if (!Isok) break; for (int y = 0; y < 3; ++y) if (sudoku[low_i + x][low_j + y] == k){ Isok = false; break; } } }
改为
if (Isok){ //检查所在小九宫格是否可以填k int up1 = ( i/3 ) * 3 + 3 ; // (i,j)方格所在的3×3小方格i坐标的上限 int up2 = ( j/3 ) *3 + 3; // (i,j)方格所在的3×3小方格在j坐标的上限 if( i % 3 == 0 ) //这是针对特殊情况的处理 up1 = i ; if( j % 3 == 0 ) up2 = j ; for( int p = up1-2 ; p <= up1 ; ++p ) /* 检查在3×3的小方格中是否出现了同一个数字 */ { if( Isok == false ) /* 跳出外层循环 */ break ; for( int q = up2-2 ; q <= up2 ; ++q ) if( sudoku[p][q] == k ) { Isok = false ; break ; } } }
再次进行性能分析,得到如下结果。
总执行时间由原来的三分钟降低到1.2秒,性能得到了很大的提高!
PSP(实际)
PSP | Personal Software Process Stages | 实际耗时(分钟) |
---|---|---|
Planning | 计划 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 30 |
Development | 开发 | 460 |
· Analysis | · 需求分析 (包括学习新技术) | 40 |
· Design Spec | · 生成设计文档 | 60 |
· Design Review | · 设计复审 (和同事审核设计文档) | 60 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 60 |
· Design | · 具体设计 | 30 |
· Coding | · 具体编码 | 120 |
· Code Review | · 代码复审 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 30 |
Reporting | 报告 | 100 |
· Test Report | · 测试报告 | 40 |
· Size Measurement | · 计算工作量 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 |
合计 | 590 |
收获与感想
这次的项目需求看似简单,但是在完成的过程中发现了很多自己没在意的事情,比如源代码管理,还有代码的性能分析等等。还有一点感触就是要把一门开发语言应用到实际项目中来,仅仅掌握基础语法是远远不够的,一些基础的函数库必须要熟悉,不然用都不知从何用起,每次搜索就浪费了很多的时间。所以,日常还是要多写写小项目和多看看算法的书。