软件工程第三次作业

github项目地址

PSP表格

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

解决思路

处理数独的目的,就是将合法的数字填满所有的空格。而所谓合法,就是该数字与所填空格纵横格和宫格内(如果存在宫)的所有数字不发生重复。

首先,建立一个9*9的二维数组map作为宫格面盘。对于size阶宫格,有size^2个格子.我们可以为所有的格子进行编号(0——size^2-1),并可以通过编号和size求出相应的横纵坐标。

0(0,0)
15(4,4)
25
36
49
64
80(8,8)

然后可以用深度搜索的方法遍历二维数组,对空格进行枚举(1-size)测试,如果合适就填入,如果不合法,就继续枚举。

/*深度搜索*/
bool sudu::Solve(int n)
{
    int num = size * size;

    /* 数独解完 */
    if (n == num)
    {
        sign = true;
        return 0;
    }

    /* 当前位非空 */
    if (map[n / size][n % size] != 0)
    {
        Solve(n + 1);
    }
    /* 试填空位 */
    else
    {
        for (int i = 1; i <= size; i++)
        {
            /* 满足条件时填入数字 */
            if (Check(n, i) == true)
            {
                map[n / size][n % size] = i;
                /* 继续下一个位置 */
                Solve(n + 1);
                if (sign == true)
                    return 0;
                /*失败还原*/
                map[n / size][n % size] = 0;
            }
        }
    }

    return false;
}

另一个点就是如何判断一个数对于空格是否合法,通过遍历其纵横格和宫格(如果存在)进行比较有无相同数字即可实现。

*判断num是否可以填入空格n*/
bool sudu::Check(int n, int num)
{
    int x = n / size;
    int y = n % size;

    /* 判断(x,y)所在横行是否合法 */
    for (int i = 0; i < size; i++)
    {
        if (map[x][i] == num)
            return false;
    }

    /* 判断(x,y)所在竖列是否合法 */
    for (int i = 0; i < size; i++)
    {
        if (map[i][y] == num)
            return false;
    }

    /* 判断(x,y)所在九宫格是否合法 */
    if (size == 9) {
        int a = x / 3 * 3;
        int b = y / 3 * 3;
        for (int i = a; i < a + 3; i++)
        {
            for (int j = b; j < b + 3; j++)
            {
                if (map[i][j] == num)
                    return false;
            }
        }
    }
    /* 判断(x,y)所在的八宫格是否合法 */
    else if (size == 8) {
        int a = x / 4 * 4;
        int b = y / 2 * 2;

        for (int i = a; i < a + 4; i++)
        {
            for (int j = b; j < b + 2; j++)
            {
                if (map[i][j] == num)
                    return false;
            }
        }
    }
    /* 判断(x,y)所在的六宫格是否合法 */
    else if (size == 6) {
        int a = x / 2 * 2;
        int b = y / 3 * 3;

        for (int i = a; i < a + 2; i++)
        {
            for (int j = b; j < b + 3; j++)
            {
                if (map[i][j] == num)
                    return false;
            }
        }
    }
    /* 判断(x,y)所在的四宫格是否合法 */
    else if (size == 4) {
        int a = x / 2 * 2;
        int b = y / 2 * 2;

        for (int i = a; i < a + 2; i++)
        {
            for (int j = b; j < b + 2; j++)
            {
                if (map[i][j] == num)
                    return false;
            }
        }
    }

    return true;
}

我很快依照初始的思路完成了代码的大体结构,可是对于文件的读写始终无法有效地实现。于是我去翻看同学的博文,发现同学有的用面对对象的方法。重新审视我面对过程的代码后,就索性按照面对对象的思路重写了代码。于是有了唯一的对象sudu。

class sudu {
public:
    /*设置数独盘*/
    void SetSaS(int s);
    /*从input文件读入数独*/
    long Read(long mark, string);
    /*将数独写入output文件*/
    void Write(string ofname);
    /*判断num是否可以填入空格n*/
    bool Check(int n, int num);
    /*深度搜索*/
    bool Solve(int n);
    /*显示数独*/
    void Display(void);
    /*键入数独,无用已被注释*/
    void keyin(void);

private:
    /*数独面盘*/
    int map[9][9];
    /*数独阶级*/
    int size;
    /*解决标志*/
    bool sign;
};

在重写过程中发现了原有代码中的许多小错误,比如在八宫格和六宫格的位置合法判断中将行列弄反。整体而言,重写后代码结构更为清晰。主函数如下:

int main(int argc, char* argv[])
{
    /*s代表宫格阶级,t代表待解答盘面数目*/
    int siz = 0, tim = 0;
    /*imark代表读取文件的偏移量*/
    long imark = 0;
    int inum, onum;

/*从命令行获取参数*/
for (int i = 0; i < argc; i++)
{
    if (strlen(argv[i]) == 1)
    {
        if (i == 2)
            siz = atoi(argv[i]);
        if (i == 4)
            tim = atoi(argv[i]);
    }
    else if (argv[i][0] == '-' && argv[i][1] == 'i') {
        i++;
        inum = i;
    }
    else if (argv[i][0] == '-' && argv[i][1] == 'o') {
        i++;
        onum = i;
    }
}
/*声明一个长度为tim的动态数独数组*/
sudu* suduzu = new sudu[tim];

/*从文件中依次读取tim个数独,解决后写入另一个文件*/
for (int i = 0; i < tim; i++)
{
    suduzu[i].SetSaS(siz);
    imark = suduzu[i].Read(imark, argv[inum]);
    //suduzu[i].keyin();
    cout << "unsolved" << endl;
    suduzu[i].Display();
    suduzu[i].Solve(0);
    cout << "solved" << endl;
    suduzu[i].Display();
    suduzu[i].Write(argv[onum]);
}

return 0;
}

这样,代码基本完成。而后我发现我的代码只能解出一个数独,百思不得其解。最终找到是因为我将DFS判断的sign声明为全局变量,在一个数独解完后并没有将状态改回去。于是我就将sign放入了sudu类中,成功解决。

关于性能分析

在修改管理员权限后,仍无法得出结果。似乎是因为我没有下载相关组件,现在还在下载(下载速度感人)。

总结

在本次作业,了解了许多的陌生概念,但是也只能说是了解,并不能说是掌握。比如我在用git时,几乎是跟着教程完成的,但是过后脑袋还是一片迷糊。所以说还是要多实践。另一方面,对c++多了一些认识,比如面对对象和文件读写。这里有许多困难,所幸大多是我可以克服的,借助同学,借助网络,借助尝试。

你可能感兴趣的:(软件工程第三次作业)