任務:實現一個可以生成數獨局而且能求解數獨問題的控制檯程序。git
一、GitHub:https://github.com/MiaoZhou48/SoftwareEngineeringProjectgithub
二、時間耗費算法
PSD2.1數組
|
Personnal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
Planning | 計劃 | 40 | 40 |
.Estimate | .估計這個任務須要多長時間 | ||
Development | 開發 | 150 | 120 |
.Analysis | .需求分析(包括學習新技術) | 180 | 200 |
.Design Spec | .生成設計文檔 | 150 | 150 |
.Design Review | .設計複審(和同事審覈設計文檔) | 60 | 60 |
.Coding Standard | .代碼規範(爲目前的開發制定合適的規範) | 60 | 60 |
.Design | 具體設計 | 80 | 80 |
.Coding | .具體編碼 | 1600 | 1800 |
.Code Review | .代碼複審 | 100 | 150 |
.Test | .測試(自我測試,修改代碼,提交修改) | 200 | 250 |
Reporting | 報告 | 120 | 150 |
.Test Report | .測試報告 | 80 | 100 |
.Size Measurement | .計算工做量 | 40 | 30 |
.Postmortem & Process Improvement Plan | .過後總結,並提出過程改進計劃 | 50 | 40 |
合計 | 2910 | 3190 |
三、解題思路:ide
此項目能夠細分爲兩個功能:生成數獨、解數獨。函數
生成數獨我考慮採用的是回溯的方法,對殘缺數獨不斷求解,從而獲得相應數量的完善的數獨。性能
肯定好方法以後即是漫長的學習之路,參考了相應的回溯算法的博客以及其餘算法的數獨生成博客。學習
在學習過程當中也見識到了不少新的思路生成數獨,好比說對一個已經合理的數獨進行變換,而後判斷其在變換以後是否依然是符合數獨規範。經過這種方式生成數獨,首先會保證結果的互異性,其次相比於回溯不須要大量的計算,節省了大量的時間。測試
可是因爲已經開始用回溯算法寫了,因此以後的改進階段會再嘗試一下敢變思路。優化
四、設計實現過程
程序主體是3個函數
judge():解數獨函數中用此函數判斷回溯條件。
solve():具體的解函數本體。
construct():構建函數。
封裝成函數有利於代碼的複用,與此同時提高了程序的簡潔性和可讀性。
函數邏輯說明:
生成數獨函數construct():首先進入函數判斷目前生成的數獨是否數量達標,若符合標準則結束函數體,不然進入下一步。下一步判斷一下1-9個數字是否通過遍歷數字全排列完,如果則跳到行間的全排列判斷,若行全排完就結束函數體,不然改變行的排列順序再初始化數排列進行終局輸出。若判斷數字全排未完,就改變數字的順序再進行終局生成。也就是兩個嵌套的判斷,數字全排和行數組的全排。
解數獨函數solve():解數獨首先加一個判斷條件,看是否已經生成了需求規定的9x9數獨,如果輸出數獨並結束;不然進行遞歸:利用judge函數判斷防止條件,能放就放並進入下一層遞歸,不能就回溯到上一層遍歷到下一個數再進行遞歸,最終遞歸結束返回最初的判斷,進行數獨輸出。
其實思路很簡單可是實際運行的話,若給定的數獨數量是1000000或更大跑的時間差很少是在20s,因此仍是有待提高的。
五、性能改進及思路
測試程序的時候,同窗建議我優化一下數字的輸出,construct函數實際上是最耗時的,因此優化也是從這裏着手。
以前使用的printf輸出格式,但其實putchar()這種以字符形式輸出會更節省時間,因此我替換了以前的printf輸出方式,結果生成時間減小到了7s左右,相較以前的20多秒提高了不少。
性能測試:
這是輸入爲1000000時的性能狀況
六、代碼說明(思路解釋+註釋說明)
旁邊的小銘同窗提供給我一種很是好的思路:就是對任意的一個終局數獨,任意交換1-三、4-六、7-9行的順序獲得的依然是知足需求的數獨,這也就彌補了8!=40320遠遠小於1000000這樣的大數字的尷尬境地。
下面是具體的construct()函數代碼
void construct(int n) { int count = 0; int trans[10] = { 0, 0, 6, 5, 4, 3, 2, 1, 7, 8 }; int Temp_column; char fn[10] = { '9','7','1','2','3','4','5','6','8','9' }; if (count < n) { for (int a = 0; a < 6; a++) { if (count == n) break; if (a) next_permutation(trans + 4, trans + 6); for (int b = 0; b < 6; b++) { if (b) next_permutation(trans + 7, trans + 9); int t = 0; char kong = ' '; do{ if (t) next_permutation(fn + 2, fn + 9); for (int i = 1; i <= 9; i++) { for (int j = 1; j <= 9; j++) { if (j - trans[i] >= 0) Temp_column = j - trans[i]; else Temp_column = j - trans[i] + 9; putchar(fn[Temp_column % 9]); if (j < 9) printf(" "); } printf("\n"); } count++; t++; if (count == n) break; else printf("\n"); } while (t<40320); if (count == n) break; } } } }
下面是具體的solve()函數代碼
void solve()//解數獨 { judge(1, 1); int flag = 0,countcolumn[10] = { 0 }; for (int i = 1; i <= 9; i++) { int counts[10] = { 0 }; for (int j = 1; j <= 9; j++) { if (!Tdarray[i][j]) { flag = 1; break; } else { if (j == 1) countcolumn[Tdarray[i][j]]++; counts[Tdarray[i][j]]++; if (counts[Tdarray[i][j]] > 1 || countcolumn[Tdarray[i][j]] > 1) { flag = 1; break; } } } if (flag) { cout << "解數獨失敗" << ""; break; } } if (!flag) { for (int i = 1; i <= 9; i++) { for (int j = 1; j <= 9; j++) { putchar(Tdarray[i][j]); if (j < 9) putchar(' '); } if (i < 9) putchar('\n'); } } }
解數獨就是要有一個判斷函數judge()用來在遍歷的階段看是否可以發放置這個數,思路就是回溯法~
七、實際耗時
具體的時間消耗見最上方的表格。