软件工程基础-个人项目

Github项目地址

首先给出我的Github项目地址:https://github.com/XiaohanSnow/sudoku

模块耗费时间估计

然后估计在程序的各个模块的开发上耗费的时间

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

解题思路描述

生成终局

通过网上查询有关数独终局生成的资料,发现一种有规律的数独终局生成方法。任意生成数独的第一行,从第二行开始,每行依次是第一行右移3、6、1、4、7、2、5、8行,可以生成满足条件的数独终局。

下面给出一个生成数独终局的例子:

在这里插入图片描述
在数独第一行第一个数字选定为2的情况下,对第一行剩下八个数字进行全排列可以生成8!=40320种数独终局。对于这个数独终局,任意交换4 5 6行中的两行或是7 8 9行中的两行,都可以生成新的数独终局。这样就可以生成40320*3!*3!=1,451,520种数独终局,满足项目的要求。

求解数独

我参考了 暴力算法之美:如何在1毫秒内解决数独问题?| 暴力枚举法+深度优先搜索 POJ 2982中采用的解数独的方法,首先遍历数独,记录下空格的位置。利用三个数组分别记录每行、每列和每个九宫格中数字的出现情况。由于采用的是深度优先搜索,所以先填可填数字数目少的方格可以有效地减少函数递归调用自身的时间。方格可填数字的数目可以由方格所在行、列和九宫格已填的数字数目之和衡量,方格所在行、列和九宫格已填的数字越多,方格可填的数字越少。

实现过程

程序的主题是两个函数

void CreateSudoku(int & n)
void SolveSudoku(string path)

主程序根据输入的类型进行判断,
分别调用CreateSudoku函数或是SolveSudoku函数。

在CreateSudoku函数中,
使用c++的next_permutation函数获取数组全排列的下一组信息。

在SolveSudoku函数中,
使用自己定义的BlockNum函数根据空格的行、列计算所在九宫格的序号 ,
使用自己定义的SetMark函数记录方格有数字,
使用自己定义的Swap函数交换空格在宫格序列中的顺序,
使用自己定义的DFS函数进行深度优先搜索,
使用自己定义的Reset函数在深度搜索结束之后将计数的数组归零。

性能优化

首先考虑生成数独的情况。在最初的版本中,我采用的是逐行写入文件的方式,采用一个大小为17的数组存储一行的数字和空格,在写入一行后写入换行,每写入9行额外换行一次。这样不可避免地会产生数量巨大次数的打开文件关闭文件的时间消耗。

从减少I/O部分时间的目标出发,我决定用一个大小为200,000,000的数组存储数独的所有内容。一个数独的数字、空格、空行总数为163,这个数组可以存储1,000,000个数独的内容。最后一次性将数组写入文件,能够有效地减少写入文件的时间。

受到生成数独中算法的启发,在解数独的过程中,我同样采用了一次性读取和一次性写入的方法,使程序的性能有了明显的提升。
在这里插入图片描述在这里插入图片描述程序中消耗最大的函数为Write函数,将数组一次性写入文件。
在这里插入图片描述
性能优化后生成1,000,000个数独的时间为1.93s,效果明显。

代码说明

从初始化的满足学号开头的数组出发,全排列后九个数字,再依次替换数独中的数字,可以得到40320个数独终局。再对这些数独终局,交换4 5 6行和7 8 9行的数字,得到符合数目要求的数独终局

void CreateSudoku(int & n)
{
    int FirstRow[] = {2,3,4,5,6,7,8,9,1};    //记录第一行的9个数 
    int RowOrder[] = {0,1,2,3,4,5,6,7,8};    //记录交换行以后行号的对应关系 
    int NumberOrder[9];                      //记录9个数字在第一行全排列变换后的对应关系 
    int NewSudoku[9][9];                     //记录第一行全排列变换后根据规则生成的新数独 
    int count = 0;                           //记录输出的个数 
    do
    {
        for (int i = 0; i < 9; i++)        //9个数字在第一行全排列变换后与原位置数字一一对应 
            NumberOrder[Sudoku[0][i] - 49] = FirstRow[i];//原位置数字的ASCII码-48等于数字,再-1等于数组序号 
        
        for (int i = 0; i < 9; ++i)        //将数独中的所有数字按照对应关系替换 
            for (int j = 0; j < 9; ++j)
                NewSudoku[i][j] = NumberOrder[Sudoku[i][j] - 49];

       
            for (int i = 0; i < 6 && n; i++)
            {
                for (int j = 0;j < 6 && n; j++)
                {
                    for (int k = 0; k < 9; k++)
                    {
                        for (int t = 0; t < 9; t++)
                        {
                            Sudoku_out[count++] = NewSudoku[RowOrder[k]][t] +'0';
                            if (t == 8)
                                Sudoku_out[count++] = '\n';     //每行末尾换行 
                            else 
                                Sudoku_out[count++] = ' ';		//方格之间空格 
                        }
                    }
                    if (--n)
                        Sudoku_out[count++] = '\n';              //数独之间空行 
                    else
                        return;
                    next_permutation(RowOrder+6,RowOrder+9); //对7 8 9行全排列变换 
                }
                next_permutation(RowOrder+3,RowOrder+6);     //对4 5 6行全排列变换 
            }
    }
    while(next_permutation(FirstRow+1,FirstRow+9));    //对第一行的后8个数全排列 
    return;
}

解决问题所要用到的数组

int unsolvedSudoku[10][10];     //记录待解决的数独 
int rowMark[10][10];            //记录数字在行中是否出现 
int colMark[10][10];            //记录数字在列中是否出现 
int blockMark[10][10];          //记录数字在九宫格中是否出现 
int row[10], col[10], block[10];//记录行、列、九宫格中出现的数字数目 
int blank[100][3];              //记录空格所在的行、列、九宫格已经填入的数字数目 
int blankCounter = 0;           //记录空格的数目 

记录方格是否有数字,将行、列、九宫格这个数字是否存在的flag置为1或0

inline void SetMark(int r, int c, int n, bool flag)  //记录方格有数字 
{
    rowMark[r][n] = flag;
    colMark[c][n] = flag;
    blockMark[BlockNum(r, c)][n] = flag;
}

深度优先搜索,深度达到空格个数则停止搜索。如果方格所在行、列、九宫格均不存在此数字,则填入,向下一深度继续搜索。如果不满足条件,进行回溯,尝试下一个数字。

bool DFS(int deep)                   //深度优先搜索 
{
    if(deep==blankCounter)            //求解完成             
    {
        return true;
    }

    int r = blank[deep][0], c = blank[deep][1];
    for(int i = 1; i < 10; i++)
    {
        if(!rowMark[r][i] && !colMark[c][i] && !blockMark[BlockNum(r, c)][i]) 
        {
            unsolvedSudoku[r][c]=i;
            SetMark(r, c, unsolvedSudoku[r][c], 1); //空格填入数字 
            if(DFS(deep+1))return true;
            SetMark(r, c, unsolvedSudoku[r][c], 0); //空格未填入数字 
            unsolvedSudoku[r][c]=0;
        }
    }
    return false;
}

遍历数独,记录空格的行号和列号并记录空格数目。给有数字的方格打上标记,方格所在行、列、九宫格中有数字方格的个数加一。

while(Sudoku_in[count_in])
    {
        for(int r = 1; r < 10; r++)
        {
            for(int c = 1; c < 10; c++)
            {
                unsolvedSudoku[r][c] = Sudoku_in[count_in++] - 48;

                if(unsolvedSudoku[r][c] == 0)   //所在方格为空格 
                {
                    blank[blankCounter][0] = r;
                    blank[blankCounter][1] = c;
                    blankCounter++;
                }
                else                            //记录方格有数字 
                {
                    SetMark(r, c, unsolvedSudoku[r][c], 1);
                    row[r]++;
                    col[c]++;
                    block[BlockNum(r, c)]++;
                }
            }
        }

GUI界面

输入数独题目,点击提交按钮后得到反馈。
在这里插入图片描述

时间统计

PSPPersonal Software
Process Stages
预估耗时
(分钟)
实际耗时
分钟
Planning计划
· Estimate· 估计这个任务需要多少时间4030
Development开发
· Analysis· 需求分析(包括学习新技术)9080
· Design Spec· 生成设计文档4050
· Design Review· 设计复审(和同事审核
设计文档)
1520
· Design Standard· 代码规范(为目前的开发
制定合适的规范)
1515
· Design· 具体设计90120
· Coding· 具体编码600500
· Code Review· 代码复审6050
· Test· 测试(自我测试,修改
代码,提交修改)
120100
Reporting报告
· Test Report· 测试报告4550
· Size Measurement· 计算工作量4520
· Postmortem & Process
Improvement Plan
· 事后总结,并提出过程
改进计划
4540
合计12051075
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值