軟件工程實踐2019第三次作業


准備工作階段


 

1,創建好Github庫后傳輸文件,Github項目地址https://github.com/leee329/031702339

2,並按要求安置好Visual Stdio 2017並做好相關文件夾

 

 

3.PSP表格

PSP2.1 Personal Software Process Stages 預估耗時(分鍾) 實際耗時(分鍾)
Planning 計划  40  40
Estimate 估計這個任務需要多少時間  10  15
Development 開發  120  120
Analysis 需求分析 (包括學習新技術)  120  90
Design Spec 生成設計文檔  20  30
Design Review 設計復審  5  5
Coding Standard 代碼規范 (為目前的開發制定合適的規范)  60  120
Design 具體設計  20  30
Coding 具體編碼  120  180
Code Review 代碼復審  30  15
Test 測試(自我測試,修改代碼,提交修改)  60  300
Reporting 報告  30  45
Test Repor 測試報告  30  40
Size Measurement 計算工作量  15  30
Postmortem & Process Improvement Plan 事后總結, 並提出過程改進計划  60  70
合計 (自閉時長) 740 1130

 

沒錯,因為回溯函數的一個等號我debug了5個小時,一共耗時1130分鍾=18h50m,自閉.jpg

開始階段


 

1,思考階段

 

 

 

一開始看到這個題目,我尋思從三宮格往外擴展,實在是太麻煩了,因為實現完三宮格之后,空間還要再擴張,那個時候代碼對於新的空間擴張就需要很大的改動,不如直接就從九宮格下手,對於九宮格來說,我得從合法性和唯一性函數以及回溯性函數來進展,其中合法性函數用於判斷某個數字在某個位置的合法性,唯一性函數用於判斷某個數在某一行/列/宮上的某個位置是否是唯一合法的,回溯函數也就是深度優先搜索函數,用於嘗試填充


 

2,算法關鍵:三大函數

計算模塊接口的設計與實現過程:

合法性函數F1:判定某個數n在某個位置(x,y)的合法性​:需要考慮到,如果x所在的這一行,y所在的這一列(x,y),n所在的這一宮全都沒有n,就是合法的。

需要考量以(x,y)為中心的十字,和一個九宮格,共21個位置(畫圖工具真難用):

例如:如圖,5在4行4列上,如果第4行和第4列和第5宮都沒有5,那么5在這里就算合法的。

 

 

 

唯一性函數F2:先說小函數f:對於每一個數,在每一個位置都進行唯一性判斷,判定某個數n,在某行或者某列或者某宮是否是唯一合法的(這一行/列/宮的其他地方都沒有n的容身之地):如果合法就填上,需要對n所在的行和列個宮的另外21個數,需要遍歷21X21次。

再說大函數F,因為每次填充都會為下一次填充提供新的條件,因此一次的f是不夠的,因為f之后又有新條件了,所以f得循環下去,但如果在其中一次f中,沒有進行填充操作,F就該結束了

例如:5在4行4列上,如果這一行(4行)中,只有(4,9)5是合法的,那么5在第四行上就算唯一的,如果在第四行上有兩個位置是合法的,那么它就不是唯一的了,如果它在(2,2)號宮里只有(5,5)是合法的,那么對於這一宮,它也是唯一的

因此(4,9)和(5,5)將會填入5

 

 

 

 試探性函數F3:因為唯一性函數以后,仍然有空位(低級題可以過,高級題仍然有空位),也就是說剩下來的所有數,在所有位置上都是不唯一的,試探性函數也就是一種回溯函數,如果這個數目前來說是合法的,就填下去,但是如果后續有數因為它的填入而無處安放,我就把這個數取出來,換另一個數。

我試着用結構體記下位置和數值供以后返回,無果,我試着用二維來定位一個位置,無果,於是我將一維來定制,比如42,對應着橫坐標42/9=4,縱坐標42%9=6

在進行唯一性函數填充以后,我從0開始試探性函數,如果被占據,直接到1,如果1在位置1上不合法,就看2。。。如果3合法,就把3填入1號位后進入2號位試探,如果2號位1-9全都不合法,回退到1號位把3拿出來,進行4的試探。。。直到第80號位都沒有出現不合法問題,就結束試探性函數。有點難解釋,直接上圖,序號表示程序順序。

 

獨到之處:通過將唯一性函數與試探性函數結合在一起,達到降低CPU消耗的效果,具體在后面大的算法改進和性能分析。

流程圖如下

 在這樣的結構下,無論是3宮格還是456789宮格,都是適用的


 

3,模塊間的關系以及計算模塊部分單元測試展示:

(siz,gl,gw分別是數獨規格和宮格規格,因此如下三個函數是針對多種數獨的求解方案)

單元測試與模塊結合

對於F1,直接對一個題目遍歷一遍就能看出測試是否出問題。

對於F2,在F1沒問題的情況下,再將1-9對81個位置進行判斷。

對於F3,和F2情況類似,因為F3和F2沒有什么關系。

具體單元測試與模塊結合如下。

合法性函數:

模塊關系:這是最基本的模塊,所以它沒有被其他模塊調用:

Locate錯誤:占位不合法。

HL錯誤:行列不合法。

GG錯誤:宮不合法。

 1 bool judge(int x, int y, int num)//坐標和數字
 2 {
 3     int i, j, t = 0, x1, y1;
 4     if (gl != 0)//如果有宮
 5         x1 = x / gl, y1 = y / gw;//宮坐標轉換
 6     if (sd[x][y] == 0)//進行位置判斷,對應下圖的LOCATE錯誤
 7     {
 8         for (i = 0; i < siz; i++)
 9             if (sd[x][i] == num || sd[i][y] == num)//進行十字判斷,對應下圖的HL型錯誤
10                 return 0;
11         if (gl != 0)
12         {
13             for (i = x1 * gl; i < x1*gl + gl; i++)//進行宮判斷,對應下圖GG型錯誤
14                 for (j = y1 * gw; j < y1*gw + gw; j++)
15                     if (sd[i][j] == num)
16                         return 0;
17         }
18         return 1;//對應下圖的成功提示
19     }
20     else
21         return 0;
22 }

  測試方法:

for(k=0;k<siz;k++)//siz是規格
	for (i = 0; i < siz; i++)
		for (j = 0; j < siz; j++)
			judge(i,j,k);

測試數據覆蓋的代碼有如上代碼和數據輸入輸出代碼。

 

 


 

 

唯一性函數: 

模塊關系/測試思路,如下:

測試數據輸入到sd[][]后,就到達了圖中的“上一模塊”,然后直接調用即可

 測試數據覆蓋的代碼除了如下代碼以外,還有合法性函數的代碼,和數據輸入輸出代碼。

 1 void single()//唯一性 
 2 {
 3     int i, j, k, l, m, t = 0, x0 = 0, y0 = 0, flag, p = 1;;
 4     while (1)
 5     {
 6         flag = 0;
 7         for (i = 1; i <= siz; i++)//對每一個數字進行唯一性判斷
 8         {
 9             for (j = 0; j < siz; j++) 
10             {
11                 t = 0; x0 = 0; y0 = 0;
12                 for (k = 0; k < siz; k++)//在某行的唯一性 
13                 {
14                     t = t + judge(j, k, i);
15                     if (judge(j, k, i))
16                         x0 = j, y0 = k;
17                 }
18                 if (t == 1)//對於行的位置上填充 
19                 {
20                     sd[x0][y0] = i;
21                     flag++;
22                 }
23                 t = 0; x0 = 0; y0 = 0;
24                 for (k = 0; k < siz; k++)//在某列的唯一性 
25                 {
26                     t = t + judge(k, j, i);
27                     if (judge(k, j, i))
28                         x0 = k, y0 = j;
29                 }
30                 if (t == 1)//對應列上的填充 
31                 {
32                     sd[x0][y0] = i;
33                     flag++;
34                 }
35             }
36             if (gl != 0)
37             {
38                 for (j = 0; j < gl; j++)
39                 {
40                     for (k = 0; k < gw; k++)//x在某宮的唯一性 
41                     {
42                         t = 0; x0 = 0; y0 = 0;
43                         for (l = gl * j; l < gl*j + gl; l++)
44                             for (m = gw * k; m < gw*k + gw; m++)
45                             {
46                                 t = t + judge(l, m, i);
47                                 if (judge(l, m, i))
48                                     x0 = l, y0 = m;
49                             }
50                         if (t == 1)//填充 
51                         {
52                             sd[x0][y0] = i;
53                             flag++;
54                         }
55                     }
56                 }
57             }
58         }
59         if (flag == 0)
60             break;
61     }
62 }

 測試方法:直接調用

 

 可以看出,簡單一點的數獨,都可以用唯一性函數做完,復雜一點的,需要用回溯法解決。

 


 

 試探性函數:

模塊關系/測試思路如下

測試數據輸入到sd[][]后,就到達了圖中的“上一模塊”,然后直接調用即可

 測試數據覆蓋的代碼除了如下代碼以外,還有合法性函數的代碼,和數據輸入輸出代碼。

 

 1 int find(int s)
 2 {
 3     int x = s / siz, y = s % siz, n;
 4     if (s > siz*siz - 1)
 5         return 1;
 6 
 7     if (sd[x][y] != 0)//每次探索都檢查有沒有占位
 8         return (find(s + 1));
 9     else
10     {
11         for (n = 1; n <= siz; n++)
12         {
13             if (judge(x, y, n))//判斷成功
14             {
15                 sd[x][y] = n;//先填充
16                 if (find(s + 1))//繼續探索
17                     return 1;
18             }
19             sd[x][y] = 0;//后取出
20         }
21     }
22     return 0;
23 }

 

測試方法:直接調用


 

4,算法/性能改進:

改進大概花費了3小時,對於試探性函數,算法的時間復雜度是O(n2n+1),對於唯一性函數算法的時間復雜度是O(n5),顯然唯一性函數會比試探性函數來的好

因此每多一個不確定的空位,試探性函數的工作時間都要增加很多,所以我放棄了一開始的只有合法性和試探性函數的結構,中間加個唯一性函數來為試探性函數減少工作時間:

VS 2017 性能測試結果如下,對於CPU占用率:

簡單題:

 

2 0 0 0 0 0 0 0 0
0 0 6 3 0 0 0 7 0
5 0 0 0 0 0 1 0 0
9 6 7 4 0 0 0 0 5 
8 1 3 0 0 0 0 0 0
4 2 0 7 1 8 9 6 3 
3 5 0 0 4 1 6 9 7
6 9 8 2 7 3 5 4 1
0 4 0 0 5 9 2 0 8

 

 

 

進行唯一性和試探性求解:

只進行試探性求解:

 

 

 

困難題:

0 0 0 0 0 0 0 0 0
5 6 4 1 0 0 0 0 0
0 0 8 0 0 0 0 6 0
1 0 0 0 8 4 7 5 0
0 0 0 2 7 0 0 0 0
7 0 0 0 0 3 1 2 0
0 0 3 0 0 0 0 7 9
0 0 7 0 0 8 0 3 0
0 9 0 0 3 0 6 0 0

 

進行唯一性和試探性求解:

只進行試探性求解:

 

 

 

對比可以發現,雙函數聯合求解與單一試探性求解在

簡單難度下的時候:

性能差別不大,有時甚至后者更優,比如圖中,唯一性函數用了3個單位的CPU使用量,才讓試探性的CPU使用量降低1個單位,指標上降低1個百分點

總體上性能的CPU使用量都為40多,其他簡單題的測試樣例也都是40-60以內,其中兩種求解方案也是不相上下;

困難難度下的時候:

性能差距拉大,前者秒殺后者:唯一性函數僅用了6個單位的CPU使用量,就將試探性函數占用的CPU大幅降低了53個單位,指標上降低27個百分點

總體性能上,差距大都由試探性函數拉開,其他困難題目的測試樣例也是如此,困難題目的CPU使用量浮動較大不便統計,一般在200±50,但都是雙函數聯合求解的方式明顯取勝

 


 

5,異常處理

對命令行參數的異常處理,針對了總參數,和數獨規格處理

    if(argc!=9) 
    {
         cout<<"總參數錯誤";
         return 0;    
     }
     else
     {
         if(siz>9||siz<3)
         {
             cout<<"規格參數錯誤";
             return 0;    
         }
     }

對最后求解完的數獨做異常處理,如果仍有空位,就會出現數獨錯誤

    for(k=0;k<time;k++)//容錯性 
    {
        for(i=0;i<siz;i++)
           for(j=0;j<siz;j++)
                if(da[k][i][j]==0)
                      cout<<"數獨錯誤"; 
    }

(siz是數獨規格,gl和gw是宮的長和寬)


 

6,總程序

在主函數中進行參數的解析和文件的打開->讀取->求解->寫入->關閉操作,其中讀取求解是多次循環,因為不止一個數獨。

#include "stdafx.h"
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<string.h>
#pragma warning(disable:4996)
using namespace std;
int sd[90][90], siz, gl, gw, time, da[10][90][90];
bool judge(int x, int y, int num)
{
    int i, j, t = 0, x1, y1;
    if (gl != 0)
        x1 = x / gl, y1 = y / gw;
    if (sd[x][y] == 0)
    {
        for (i = 0; i < siz; i++)
            if (sd[x][i] == num || sd[i][y] == num)
                return 0;
        if (gl != 0)
        {
            for (i = x1 * gl; i < x1*gl + gl; i++)
                for (j = y1 * gw; j < y1*gw + gw; j++)
                    if (sd[i][j] == num)
                        return 0;
        }
        return 1;
    }
    else
        return 0;
}
void single()
{
    int i, j, k, l, m, t = 0, x0 = 0, y0 = 0, flag, p = 1;;
    while (1)
    {
        flag = 0;
        for (i = 1; i <= siz; i++)
        {
            for (j = 0; j < siz; j++)
            {
                t = 0; x0 = 0; y0 = 0;
                for (k = 0; k < siz; k++)
                {
                    t = t + judge(j, k, i);
                    if (judge(j, k, i))
                        x0 = j, y0 = k;
                }
                if (t == 1)
                {
                    sd[x0][y0] = i;
                    flag++;
                }
                t = 0; x0 = 0; y0 = 0;
                for (k = 0; k < siz; k++)
                {
                    t = t + judge(k, j, i);
                    if (judge(k, j, i))
                        x0 = k, y0 = j;
                }
                if (t == 1)
                {
                    sd[x0][y0] = i;
                    flag++;
                }
            }
            if (gl != 0)
            {
                for (j = 0; j < gl; j++)
                {
                    for (k = 0; k < gw; k++)
                    {
                        t = 0; x0 = 0; y0 = 0;
                        for (l = gl * j; l < gl*j + gl; l++)
                            for (m = gw * k; m < gw*k + gw; m++)
                            {
                                t = t + judge(l, m, i);
                                if (judge(l, m, i))
                                    x0 = l, y0 = m;
                            }
                        if (t == 1)
                        {
                            sd[x0][y0] = i;
                            flag++;
                        }
                    }
                }
            }
        }
        if (flag == 0)
            break;
    }
}
int find(int s)
{
    int x = s / siz, y = s % siz, n;
    if (s > siz*siz - 1)
        return 1;

    if (sd[x][y] != 0)
        return (find(s + 1));
    else
    {
        for (n = 1; n <= siz; n++)
        {
            if (judge(x, y, n))
            {
                sd[x][y] = n;
                if (find(s + 1))
                    return 1;
            }
            sd[x][y] = 0;
        }
    }
    return 0;
}
int main(int argc, char *argv[])
{
    FILE* fp;
    int i, j, k, l, m, t = 0, x0 = 0, y0 = 0;
    char *x;
    char *y; 
    for (i = 0; i < argc; i++)//參數讀取 
    {
        if (strlen(argv[i]) == 1)
        {
            if (i == 2)
                siz = atoi(argv[i]);//讀取規格 
            if (i == 4)
                time = atoi(argv[i]);//讀取數獨個數 
        }
        if(i==6)
            x=argv[i];
        if(i==8)
            y=argv[i];
    }
    if (siz % 3 == 0)//宮的規格的轉化 
    {
        gl = siz / 3;
        if (gl != 0)
            gw = siz / gl;
    }
    if (siz % 2 == 0)
    {
        gl = siz / 2;
        if (gl != 0)
            gw = siz / gl;
    }
    if(siz==6)
        gl=2,gw=3;
    if(argc!=9) 
    {
        cout<<"總參數錯誤";
        return 0;    
    }
    else
    {
        if(siz>9||siz<3)
        {
            cout<<"規格參數錯誤";
            return 0;    
        }
    }
    fp = fopen(x, "r");//文件讀取 
    if (fp == NULL) //為空時返回錯誤 
        return -1;
    for (k = 0; k < time; k++)
    {
        i = 0; j = 0;
        while (i != siz) //將每一個數獨划開 
        {
            fscanf(fp, "%d", &sd[i][j]);
            j++;
            if (j == siz)
            {
                j = 0;
                i++;
            }
        }
        single();
        t = find(0);
        for (i = 0; i < siz; i++)
            for (j = 0; j < siz; j++)
                da[k][i][j] = sd[i][j];//解決后存入da三維數組 
    }
    fclose(fp);
    fp = fopen(y, "w");
    if (fp == NULL)
        return -1;
    for (k = 0; k < time; k++)
    {
        for (i = 0; i < siz; i++)
        {
            for (j = 0; j < siz; j++)
            {
                fprintf(fp, "%d", da[k][i][j]);//依次遞交 
                if (j != siz - 1)
                    fprintf(fp, " ");
            }
            if (i != siz - 1)
                fprintf(fp, "\n");
        }
        if (k != time - 1)
            fprintf(fp, "\n\n");
    }
    fclose(fp);
    for(k=0;k<time;k++)//容錯性 
    {
        for(i=0;i<siz;i++)
            for(j=0;j<siz;j++)
                if(da[k][i][j]==0)
                    cout<<"數獨錯誤"; 
    }
    return 0;
}

測試:

例1:

E:\軟工\031702339\Sudoku\Sudoku>Sudoku.exe -m 9 -n 2 -i input.txt -o output.txt

  

 

 

 

 例2:

E:\軟工\031702339\Sudoku\Sudoku>Sudoku.exe -m 3 -n 1 -i input.txt -o output.txt

  

 

例3:

E:\軟工\031702339\Sudoku\Sudoku>Sudoku.exe -m 8 -n 2 -i input.txt -o output.txt

  

 

例456789:

 

例10(異常):


 

總結:一步一步過來,遇到了很多問題,所以也確確實實學習到了很多,學到了Github的文件上傳,VS性能測試,VS的E0266,C4996,LNK2019,LNK2005,C2085等等各種奇葩報錯或警告的解決辦法,還有函數定制的方式改善,還有文件傳輸的具體規范等等很多很多,同時也意識到項目制定的規范性。

面對這些問題,有的繞開了,有的迎難而上,這樣的處理方式不由得令我反觀試探性函數,我不正如它一樣不斷的尋錯,不斷的改進嗎?

 好啦,為期20h的周常自閉結束。容錯性函數或將改善。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM