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天的時候纔開始作的,作起來就像在和時間賽跑,因此還有不少地方作的不足,在以後的做業中這點仍是須要注意。

相關文章
相關標籤/搜索