软件工程第二次作业——个人项目

本文详细介绍了使用回溯法实现数独生成的过程,包括解题思路、设计实现、代码解析及性能优化等内容。

一、Github项目地址

二、在开始实现程序之前,在PSP表格记录下你估计将在程序的各个模块的开发上耗费的时间。

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

三、解题思路描述。即刚开始拿到题目后,如何思考,如何找资料的心路历程。

   刚开始看到题目时,就感觉好难的样子,完全没有思路,不知从何下手,感觉挺复杂的。最开始就是想在每一格中随机填数,然后再进行验证,感觉要验证的挺多的,很复杂,对数独又比较陌生,就去网上搜了些生成数独的方法。看到了一种变换的方法,感觉没有那么复杂,就是先写出第一个数组,在第一个数组的基础上进行变换,可以左右列进行交换,可以上下行进行交换,还可以不同数字之间进行交换,不同的交换方式增加了数独的随机性,但我觉得这种数独产生的方式随机性不是很好,都是在第一个的基础上变换而来,因此每个数据也会长得极为相似,变换回原来的数独的概率也是蛮大的,当生成很多数独时,就有很大概率会重复,就无法满足题目中不重复的要求。由于每行每列每个九宫格都不会有重复的数字,后来有想过一个数字一个数字地放置,将1从第一行开始放,放到最后一行,把1放完之后再放数字2,感觉这也是可行的,但是位置的选择挺麻烦的,我还没发现它们之间的规律,即每个数字要如何放置在合理的位置,且对接下来数字的影响较小,位置不好找,因此这种想法没有进行尝试。
   后来在网上看到了随机法和回溯法,我都尝试着打了一遍,感觉回溯法效率会高一些。随机法是每个位置都从1-9中随机选一个数放置,再判断是否合理,合理就放置,不合理就将该数删除,在剩下的数中重新找过,这跟我一开始未成形的想法有些相似。当随机法生成数独失败后,将会重新生成数独,这点跟回溯法不一样,如果某个数放置得不合理,可以退回去修改前面的数,使得后面的数变得合理,因此无需重新生成,效率高了许多。

四、设计实现过程。设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?

代码总共有一个sudoku类,该类中包含四个函数:

void init();//初始化棋盘
void print();//打印数独棋盘
bool isRightposition(int row, int col);//验证是否可以放置
bool generate(int mark);//生成数独

最核心的函数是generate(),在运行过程中它会不断回溯,并判断位置是否合理(isRightpositio()))。每次生成数独之前都要初始化(init())。

  • 以下是各函数之间的流程图:

885528-20170913133335907-1661516797.png

五、代码说明。展示出项目关键代码,并解释思路与注释说明。

比较核心的代码是isRightposition()和generate()

  • isRightposition()代码部分

思路:每填入一个数字都要进行验证,主要验证以下三个方面:
1、同行不同列数字重复;
2、同列不同行数字不能重复;
3、每个九宫格的数字不能重复

bool isRightposition(int row, int col)
{
    int curNum = shudu[row][col];
        //验证行
    for (int i = 0; i < row; i++)
    {
        if (shudu[i][col] == curNum)
        {
            return false;
        }
    }
    //验证列
    for (int i = 0; i < col; i++)
    {
        if (shudu[row][i] == curNum)
        {
            return false;
        }
    }
    //验证九宫格 
    int rowStart = row / 3 * 3;
    int rowEnd = rowStart + 2;
    int colStart = col / 3 * 3;
    int colEnd = colStart + 2;
    for (int i = rowStart; i <= rowEnd; i++)
    {
        for (int j = colStart; j <= colEnd; j++)
        {
            if (shudu[i][j] == curNum && i != row && j != col)
            {
                return false;
            }
        }
    }
}
  • generate()代码部分

思路:采用回溯法,将每个格子进行编号,总共0-80。用一个长度为9的数组的0、1状态记录随机数1-9的使用情况。每次随机生成一个下标,判断该下标对应的数是否为0,若不是,表示该下标作为随机数已被用过,重新选择一个随机数,0表示数未被使用过,则将该下标作为随机数赋给数独,之后进行判断,该位置是否合理,合理则继续生成下一个数,不合理则回溯,重新生成上一个数,如此循环,直到mark == 81时表示数独生成完毕。

//采用回溯法,将格子进行编号(0-80)
bool generate(int mark)
{
    if (mark == 81)
    {
        return true;
    }    
    int row = mark / 9;//行坐标
    int col = mark % 9;//列坐标    
    //若当前位置已填入数字,则继续往下生成
    if (shudu[row][col] != 0)
    {
        if (generate(mark + 1))
        {
            return true;
        }
    }
    else
    {
        int count = 0;
        int index;
        //arr用来标记数字是否被使用 ,0表示未用,1表示已用
        int arr[9] = { 0 };
        for (int i = 0; i < 9; i++)
        {
            //生成随机数字,直到1到9全部生成完毕 
            while (1)
            {
                index = rand() % SIZE + 1;
                if (arr[index - 1] == 0)
                {
                    arr[index - 1] = 1;
                    count++;
                    break;
                }
            }
            shudu[row][col] = index;
            //判断赋值是否合法,合法则继续生成下一位置
            if (isRightposition(row, col))
            {
                if (generate(mark + 1))
                {
                    return true;
                }
            }
            else
            {
                //不合法,回溯
                shudu[row][col] = 0;
            }
        }
    }
    return false;
}

六、测试运行。程序必须是可运行的,展示出程序运行的截图。

  • 输入不合法的情况

885528-20170913112826157-2124810527.png

  • 运行结果演示

885528-20170913113001157-291102650.png

七、记录在改进程序性能上所花费的时间,描述你改进的思路,并展示一张性能分析图,并展示你程序中消耗最大的函数。

之前都没用过性能分析这种东西,完全就是一脸懵逼,看了《构建之法》之后还是没有很清楚,上网搜了一下,也挺混乱的。后来在慢慢的摸索下貌似弄了出来,但也不知道对不对,花了挺多时间的。

  • 性能分析

885528-20170913121945500-1007856056.png

885528-20170913121859797-472283563.png

885528-20170913122040016-1093152493.png

885528-20170913122212282-1636636438.png

885528-20170913122328532-1588445863.png

885528-20170913122412188-1670660767.png

885528-20170913122620641-1790528149.png

从以上图中可以看出,print()是消耗最大的函数,其次是generate()。跟我预想的不一样,一开始以为generate()会消耗最多,因为要一直循环验证。后来尝试着将输出部分用C来写,感觉速度有快些,但不明显。而且我发现,有些时间偏差还是挺大的,不知道是电脑太卡了还是代码的问题,我觉得最有可能影响运行速度的应该是generate()函数,因为随机数是随机生成的,要一直随机到满足条件位置,我感觉这里的时间是不太稳定的。

  • 单元测试
#include "stdafx.h"
#include "CppUnitTest.h"
#include"../sudoku/sudoku.h"    
using namespace Microsoft::VisualStudio::CppUnitTestFramework;    
namespace UnitTest1
{
    TEST_CLASS(UnitTest1)
        {
    public:    
        TEST_METHOD(TestisRightposition)
        {
            // TODO: 在此输入测试代码
            sudoku sudo;
            sudo.init();
            bool pos = sudo.isRightposition(3, 3);
            Assert::IsTrue(pos == false);
        }    
    };
}

885528-20170913171618094-170208252.png

八、在你实现完程序之后,在PSP表格记录下你在程序的各个模块上实际花费的时间。

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

总结

   感觉这次作业挺难的,有很多新东西是我从来都没有接触过的,虽然之前也有用VS进行编程,但是从来没有用过性能分析和单元测试。一开始写数独就写了挺久的,传github也出现了一些问题,不过最终还是完成了,感觉自己还是学到了许多东西的,对github的使用更加熟练了,也知道了vs更多的功能,不只是纯粹用来打代码。

转载于:https://www.cnblogs.com/leijing/p/7513069.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值