2019软件工程实践——第三次做业

GitHub地址

https://github.com/wujunjie1008/031702537.gitgit

P2P表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30min 15min
Estimate 估计这个任务须要多少时间 24h 26h
Development 开发 5h 2h
Analysis 需求分析 (包括学习新技术) 2h 2h
Design Spec 生成设计文档 15min 30min
Design Review 设计复审 1h 30min
Coding Standard 代码规范 (为目前的开发制定合适的规范) 10min 15min
Design 具体设计 1h 2h
Coding 具体编码 4h 6h
Code Review 代码复审 2h 4h
Test 测试(自我测试,修改代码,提交修改) 3h 4h
Reporting 报告 3h 3h
Test Repor 测试报告 30min 1h
Size Measurement 计算工做量 15min 15min
Postmortem & Process Improvement Plan 过后总结, 并提出过程改进计划 30min 15min
总计 23.16h 26h

需求

实现一个多阶数独解题工具。github

数独

数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出必定的已知数字和解题条件,利用逻辑和推理,在其余的空格上填入1-9的数字。使1-9每一个数字在每一行、每一列和每一宫中都只出现一次,因此又称“九宫格”。
算法

初期准备——磨刀不误砍柴工

因为此次代码的要求是要cmd命令传入参数而且须要文件输入输出的格式,所以我就去百度,没想到居然顺便解决了我以前一直有的疑问函数

int main(int   argc, char*   argv[])

原来main()函数里的第一个参数argc正是对应用cmd命令输入参数的个数,而第二个参数argv[]对应的则是这些参数的字符串形式
参考了规定的cmd输入后,我写出了一下导入参数的代码工具

char* i;
    char* o;
    int m, n, h, l;

    m = atoi(argv[2]);
    n = atoi(argv[4]);
    i = argv[6];
    ifstream fin(i);
    if (!fin.is_open())
    {
        cout << "输入文件不存在";
        return 0;
    }
    o = argv[8];
    ofstream fout(o);

文件输入:性能

h = 0;
        while (!fin.eof()) {            //文件输入
            getline(fin, list);
            if (list.length() != 2 * m && list.length() != 2 * m - 1) {     //表格输入不规范,与阶数m不相符则退出
                cout << "表格大小不符合";
                return 0;
            }
            for (l = 0; l < m; l++) {
                num_list[h][l] = list[l * 2] - 48;
                if (num_list[h][l] < 0 || num_list[h][l]>9) {       //表格输入不规范,出现非数字字符则退出
                    cout << "九宫格中出现不是0-9的数字";
                    return 0;
                }
            }
            h++;

文件输出:单元测试

for (h = 0; h < m; h++) {           //文件输出
            for (l = 0; l < m; l++) {
                fout << num_list[h][l];
                if (l != m - 1)
                    fout << ' ';
                else
                    fout << '\n';
            }
        }
        fout << '\n';

解题思路——山重水复疑无路

对于我我的来讲,若是让我来作数独,我比较喜欢用惟余法,及经过该格子的同一行,同一列,同一宫的数来推断出该空格能够填什么数字,而对于难度较低的数独来讲,必定会有一个空格只能填一个数字,当这个数字被填入后,就会出现另外一个只能填一个数字的格子,所以,我以为能够将其应用到低阶还有难度较低的9宫格数独中。具体代码以下:学习

int weiyu(int m) {
    cout << "使用惟余法" << endl;
    int h, l;
    int may_count;
    int count = 0;
    for (h = 0; h < m; h++) {
        for (l = 0; l < m; l++) {
            if (num_list[h][l] == 0)
                count++;
        }
    }
    for (h = 0; h < m; h++) {
        for (l = 0; l < m; l++) {
            if (num_list[h][l] == 0) {
                int may_num[10] = { 0 };
                may_count = m;
                for (int bh = 0; bh < m; bh++) {        //遍历行
                    if (num_list[bh][l] != 0) {
                        if (may_num[num_list[bh][l]] == 0)
                            may_count--;
                        may_num[num_list[bh][l]] = 1;
                    }
                }
                for (int bl = 0; bl < m; bl++) {        //遍历列
                    if (num_list[h][bl] != 0) {
                        if (may_num[num_list[h][bl]] == 0)
                            may_count--;
                        may_num[num_list[h][bl]] = 1;
                    }
                }
                if (m == 4 || m == 6 || m == 8 || m == 9) {             //遍历宫格
                    int max_h = 0, max_l = 0;
                    int gong_h = 0, gong_l = 0;

                    //定位当前格在属于第几宫格
                    if (m == 4 || m == 8 || m == 9) {
                        gong_l = (int)sqrt(m);
                        gong_h = (int)(m / gong_l);
                    }
                    else if (m == 6) {
                        gong_h = (int)sqrt(m);
                        gong_l = (int)(m / gong_h);
                    }
                    for (int i = 1; i < m; i++) {
                        max_h = i * gong_h;
                        if (max_h > h) {
                            break;
                        }
                    }
                    for (int i = 1; i < m; i++) {
                        max_l = i * gong_l;
                        if (max_l > l) {
                            break;
                        }
                    }

                    //开始遍历
                    for (int i = max_h - gong_h; i < max_h; i++) {
                        for (int j = max_l - gong_l; j < max_l; j++) {
                            if (i > 9)      //消除vs编译器的警告,删去不影响代码
                                i = 9;
                            if (i < 0)
                                i = 0;
                            if (j > 9)
                                j = 9;
                            if (j < 0)
                                j = 0;
                            if (num_list[i][j] != 0) {
                                if (may_num[num_list[i][j]] == 0)
                                    may_count--;
                                may_num[num_list[i][j]] = 1;
                            }
                        }

                    }
                }
                if (may_count == 1) {                   //填写数字
                    for (int i = 1; i <= m; i++) {
                        if (may_num[i] == 0) {
                            num_list[h][l] = i;
                            h = 0;
                            l = -1;
                            count--;                //填写成功,未填数减一
                            if (count == 0) {
                                printf("成功\n");
                                return 0;       //完成数独,返回0
                            }
                            break;
                        }
                    }
                }
            }
        }
    }
    return 1;       //未完成数独,返回1
}

本觉得已经大功告成的我却由于输入一道更高难度的数独题目以后,心情又跌落到了谷底。(附上原题)
0 0 8 0 9 0 0 0 0
0 7 0 0 0 0 2 8 0
0 6 4 1 0 0 3 0 9
0 0 0 8 0 5 9 0 0
5 0 0 0 0 0 0 0 1
0 0 9 3 0 4 0 0 0
8 0 2 0 0 7 5 6 0
0 9 7 0 0 0 0 1 0
0 0 0 0 6 0 7 0 0
惟余法是能够用,可是一旦出现解不惟一,或者没有格子是填惟一一个数的时候惟余法根本没法解出答案。仅仅只是作出简单数独,那和咸鱼有什么区别,因而我又开始寻找新的方法。测试

解题思路——柳暗花明又一村

自闭是想新方法时候的主旋律,脑子在想的是总不能一格一格的试过去吧。后来无心间听到周围的人都在说DFS(深度优先算法),当时还很困惑,DFS和数独有什么关系。再后来发现DFS不正是把每一种可能都试过去嘛。可是我对于DFS的运行时间仍是有着一丝担心。抱着试试看的心态,我开始写DFS的函数。
——首先是检查函数,我在原来惟余法的基础上进行改变,就得出如今的check()函数:优化

int check(int h, int l, int m) {
    int num[10] = { 0 };
    for (int bh = 0; bh < m; bh++) {        //遍历行,寻找重复的数字
        if (num_list[bh][l] != 0) {
            num[num_list[bh][l]]++;
            if (num[num_list[bh][l]] > 1)
                return 0;
        }
    }
    for (int i = 0; i < 10; i++) {
        num[i] = 0;
    }
    for (int bl = 0; bl < m; bl++) {        //遍历列,寻找重复的数字
        if (num_list[h][bl] != 0) {
            num[num_list[h][bl]]++;
            if (num[num_list[h][bl]] > 1)
                return 0;
        }
    }
    for (int i = 0; i < 10; i++) {
        num[i] = 0;
    }
    if (m == 4 || m == 8 || m == 9 || m == 6) {         //检验九宫格,寻找重复的数字
        
        //定位该单元格所属的宫格
        int max_h = 0, max_l = 0;                   
        int gong_h = 0, gong_l = 0;
        if (m == 4 || m == 8 || m == 9) {
            gong_l = (int)sqrt(m);
            gong_h = (int)(m / gong_l);
        }
        else if (m == 6) {
            gong_h = (int)sqrt(m);
            gong_l = (int)(m / gong_h);
        }
        for (int i = 1; i < m; i++) {
            max_h = i * gong_h;
            if (max_h > h) {
                break;
            }
        }
        for (int i = 1; i < m; i++) {
            max_l = i * gong_l;
            if (max_l > l) {
                break;
            }
        }
        
        //正式开始遍历
        for (int i = max_h - gong_h; i < max_h; i++) {
            for (int j = max_l - gong_l; j < max_l; j++) {
                if (num_list[i][j] != 0) {
                    num[num_list[i][j]]++;
                    if (num[num_list[i][j]] > 1)
                        return 0;       //有重复,返回0
                }
            }
        }
    }

    return 1;       //无重复,返回1
}

——接着就是咱们的主角DFS(深度优先搜索)函数了:

int DFS(int n, int m){
    if (n > (m*m)) {
        return 1;
    }
    if (num_list[n / m][n % m] != 0){       // 不须要填数字,则跳过
        if (DFS(n + 1, m) == 1)
            return 1;
    }
    else{
        for (int i = 1; i <= m; i++){       //试填1-9的数字 
            num_list[n / m][n % m] = i;
            if (check(n / m, n % m, m) == 1){
                if (DFS(n + 1, m) == 1)
                    return 1;               //返回时若是构形成功,则返回1
                else
                    num_list[n / m][n % m] = 0;
            }
            else
                num_list[n / m][n % m] = 0;
        }
    }
    return 0;
}

没想到看似复杂的思想在用代码实现后能够这么短,最后的结果就是成功的,更高难度的数独也被我拿下了。
3 5 8 7 9 2 1 4 6
9 7 1 4 3 6 2 8 5
2 6 4 1 5 8 3 7 9
7 2 6 8 1 5 9 3 4
5 4 3 6 7 9 8 2 1
1 8 9 3 2 4 6 5 7
8 1 2 9 4 7 5 6 3
6 9 7 5 8 3 4 1 2
4 3 5 2 6 1 7 9 8

寻求改进

一方面出于对DFS运行速度的不放心,另外一方面不想让我以前写的惟余法函数就这个被删除,我开始用vs自带的性能探查器探查他们运行时间的对比:

——首先是单独使用DFS的函数运行时间:

——接着是先使用惟余法若无解再使用DFS的函数运行时间:

能够看出尽管测试问题中,只有三个问题必定须要用到DFS,可是使用惟余法加DFS的策略明显比单纯用dfs要来的快,所以证实惟余法和DFS结合在时间上具备更高的效率。

单元测试

void test(int m) {
    int h, l;
    int may_num[10] = {0};
    for (h = 0; h < m; h++) {
        for (l = 0; l < m; l++) {
            if (num_list[h][l] == 0) {
                cout << "失败" << endl;
                return;
            }
            else {
                                for (int i = 1; i < 10; i++)
                    may_num[i] = 0;
                for (int bh = 0; bh < m; bh++) {        //遍历行
                    if (num_list[bh][l] != 0) {
                        may_num[num_list[bh][l]] ++;
                        if (may_num[num_list[bh][l]] > 1){
                            cout << "行失败" <<bh<<' '<<l<< endl;
                            return;
                        }
                    }
                }
                for (int i = 1; i < 10; i++)
                    may_num[i] = 0;
                for (int bl = 0; bl < m; bl++) {        //遍历列
                    if (num_list[h][bl] != 0) {
                        may_num[num_list[h][bl]] ++;
                        if (may_num[num_list[h][bl]] > 1) {
                            cout << "列失败" << h << ' ' << bl << endl;
                            return;
                        }
                    }
                }
                for (int i = 1; i < 10; i++)
                    may_num[i] = 0;
                if (m == 4 || m == 6 || m == 8 || m == 9) {             //遍历宫格
                    int max_h = 0, max_l = 0;
                    int gong_h = 0, gong_l = 0;

                    //定位当前格在属于第几宫格
                    if (m == 4 || m == 8 || m == 9) {
                        gong_l = (int)sqrt(m);
                        gong_h = (int)(m / gong_l);
                    }
                    else if (m == 6) {
                        gong_h = (int)sqrt(m);
                        gong_l = (int)(m / gong_h);
                    }
                    for (int i = 1; i < m; i++) {
                        max_h = i * gong_h;
                        if (max_h > h) {
                            break;
                        }
                    }
                    for (int i = 1; i < m; i++) {
                        max_l = i * gong_l;
                        if (max_l > l) {
                            break;
                        }
                    }

                    //开始遍历
                    for (int i = max_h - gong_h; i < max_h; i++) {
                        for (int j = max_l - gong_l; j < max_l; j++) {
                            if (num_list[i][j] != 0) {
                                may_num[num_list[i][j]] ++;
                                if (may_num[num_list[i][j]] > 1) {
                                    cout << "宫失败" << i << ' ' << j << endl;
                                        return;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    cout << "成功"<<endl<<endl;
}

以上是我用来检验DFS的测试函数,当出现错误时,效果以下:

3阶:

4阶:

5阶:

6阶:

7阶:

8阶:

9阶:


总结

经过此次的实践做业,我学会了对于本身的项目,不只仅要作出来,还须要对其debug,作屡次测试,以及优化,就像我最后引入了DFS算法同样,并且我还学会了用VS2017的性能探查器来对代码的各个部分作出检测,来优化个人代码。不只如此,我还学会应该合理分配个人时间,我是在距离deadline还有4天的时候才开始作的,作起来就像在和时间赛跑,因此还有不少地方作的不足,在以后的做业中这点仍是须要注意。

相关文章
相关标签/搜索