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