软件工程2017第二次作业
GitHub:sudoku
解題思路描述
剛看到題目的時候,我去,好難。吃了根冰棍冷靜下來,開始細細思考。題目的要求是隨機生成N個不重復的數獨棋盤,有兩種方案:1.用數字1~9填滿第一個九宮格,然后再去填下一個九宮格,直到九個九宮格都填滿,并且不會每一行每一列不會有相同的數字。2.用數字1填第一個九宮格,然后再填第二個.......直到九個九宮格都填入了1,再把數字變成2,再一個個地去填九宮格,以此類推,直到9個數字都填入。我認為第2種方案實現起來會更容易一些,因此決定采用第二種方案。
接下來便是確定數據結構和方法了。如果單純采用二維數組來實現遞歸的話,我不知道該如何表示已經遍歷過的格子。因此我決定采用鏈表加上二維數組的方式來實現數獨棋盤的生成。
具體的方法如下:每個九宮格做一個含有九個結點的鏈表,Grid[g]存儲第g個九宮格鏈表的首結點,blocks[g]用來表示第g個九宮格中空閑位置的數量(不包括已經嘗試過的結點),遞歸函數PutNum和GetRandomValue互相配合往合適位置填入數字,當一個九宮格中無法填入數字,則向上一個宮返回false,如果可以則繼續往下一個九宮格填數字。如果每次都是用這種遞歸方式隨機生成數獨棋盤,這樣子效率太低,于是我想到一個方法,每隨機生成一個數獨棋盤之后,可以調換數字,這樣就又成了一個新的數獨棋盤,考慮到左上角的數字是固定的,所以這種換數字大法可以在一個隨機數獨棋盤的基礎上生成40320種不同的棋盤。要實現這種換數字大法,就必須獲取每種全排列的順序,我使用permutation函數來生成全排列并將獲取到的數據填入arr2中。
設計實現
代碼中的全局變量弄得有點多,雖然我知道這樣不好,但是不這樣弄得話又感覺很不方便。代碼中共有6個函數:BuildLinkedList()是用來建立存儲坐標的鏈表;GetRandomValue(short g)是用來在第g個九宮格中放置數字num,當沒有合法位置放置時,返回false;PutNum(short g)是用來遞歸調用的函數,該函數調用GetRandomValue來放置數字num;ShowSudoku()是用來將數獨棋盤輸出到文本文件中的函數;Permutation(short length)是用來產生全排列數組的函數;Clean()函數是當無法生成數獨棋盤的時候,對一些動態變量進行清理,防止內存泄漏。
代碼說明
這個是用來在九宮格中隨機選取空閑可用位置的函數
bool GetRandomValue(short g)//在第g個九宮格中隨機選取可用的位置來放入數字 {if (blocks[g] == 0)return false;int value;value = rand() % blocks[g];//生成隨機數int i;Node *p1, *p2;for (i = 0, p2 = Grid[g], p1 = p2; i < 2 * blocks[g] - 1; i++)//p2即為可放置數字的位置坐標{if (i >= value&&row_flag[p2->row] == false && column_flag[p2->column] == false)break;if (i == blocks[g] - 1)p2 = Grid[g], p1 = p2;else{p1 = p2;p2 = p2->next;}}if (i == 2 * blocks[g] - 1)return false;sudoku[p2->row][p2->column] = num;//接下來的代碼是對被選中的目標位置結點進行刪除前的準備操作numlocation[g] = 3 * (p2->row % 3) + (p2->column % 3);row_flag[p2->row] = true;column_flag[p2->column] = true;if (p2 == Grid[g])Grid[g] = p2->next;if (p2 == LinkedListTail[g])LinkedListTail[g] = p1, p1->next = NULL;if (p2 != Grid[g] && p2 != LinkedListTail[g])p1->next = p2->next;delete p2;blocks[g] = blocks[g] - 1;return true; }調用GetRandomValue函數在每個宮內放置數字的函數PutNum
bool PutNum(short g)//在每個九宮格中放入相應的數字num {for (;;){if (GetRandomValue(g) == true){if (g == 8)return true;else if (PutNum(g + 1) == false)//如果PutNum(g+1)返回false,則說明第g+1個宮無法放置數字,則在第g個宮嘗試可以放置的其他位置,已經嘗試過的位置結點則放置到鏈表后面{Node *p = new Node;p->row = 3 * (g / 3) + numlocation[g] / 3;p->column = 3 * (g % 3) + numlocation[g] % 3;p->next = NULL;LinkedListTail[g]->next = p;LinkedListTail[g] = p;sudoku[p->row][p->column] = 0;row_flag[p->row] = false;column_flag[p->column] = false;continue;}else{blocks[g] = 9 - num;return true;}}else{blocks[g] = 10 - num;return false;}} }生成全排列并存放在arr2數組中的函數Permutation
void Permutation(short length)//用遞歸的方法在arr2數組中生成全排列 {int i;if (length == 10 - sudoku[0][0]){if (length != 1)Permutation(length - 1);elseShowSudoku();return;}for (i = 0; i<9 && stop_flag; i++){if (arr1[i] == 0){arr1[i] = 1;arr2[9 - length] = i + 1;if (length != 1)Permutation(length - 1);elseShowSudoku();arr1[i] = 0;}} }將數獨棋盤輸出到文本文件中
void ShowSudoku()//將數獨棋盤輸出到文本文件中 {int row, column;for (row = 0; row < 9; row++){for (column = 0; column < 8; column++){fcout << arr2[sudoku[row][column] - 1] << " ";//將隨機生成的數獨棋盤映射到arr2數組中}fcout << arr2[sudoku[row][column] - 1] << endl;}if (--sudoku_count == 0)stop_flag = false;elsefcout << endl; }測試運行
測試運行的截圖
效能分析與改進
分析時生成的數獨棋盤個數設為50000個
使用鏈表來進行生成數獨棋盤真的很費時,效率不高,而且又占用空間。如果時間允許的話,我想不用鏈表來做,但是不用鏈表如何表示已經遍歷過的位置對我來說是個問題,寫完這篇博客準備去研究一下。
PSP表格
| Planning | 計劃 | 720 | 1200 |
| · Estimate | · 估計這個任務需要多少時間 | 720 | 1200 |
| Development | 開發 | 660 | 1020 |
| · Analysis | · 需求分析 (包括學習新技術) | 120 | 120 |
| · Design Spec | · 生成設計文檔 | 0 | 0 |
| · Design Review | · 設計復審 (和同事審核設計文檔) | 0 | 0 |
| · Coding Standard | · 代碼規范 (為目前的開發制定合適的規范) | 30 | 30 |
| · Design | · 具體設計 | 60 | 180 |
| · Coding | · 具體編碼 | 360 | 600 |
| · Code Review | · 代碼復審 | 30 | 30 |
| · Test | · 測試(自我測試,修改代碼,提交修改) | 60 | 60 |
| Reporting | 報告 | 60 | 180 |
| · Test Report | · 測試報告 | 0 | 0 |
| · Size Measurement | · 計算工作量 | 30 | 60 |
| · Postmortem & Process Improvement Plan | · 事后總結, 并提出過程改進計劃 | 30 | 120 |
| 合計 | 720 | 1200 |
個人總結
第二次作業對我個人來講難度還是很大的,這次代碼用的數據結構也不是很好,處理鏈表帶來的時間和空間開銷都比較大。生成一個數獨棋盤后替換數字又生成了另外一個棋盤是一個比較取巧的辦法,時間開銷比遞歸生成數獨棋盤所用的時間要小,因此我采用了遞歸生成數獨和換數字相結合的方式。還有就是這個完成這個作業的耗時遠遠在我的意料之外,花的時間實在是太長了,主要是前期規劃不怎么好,導致編碼的時候bug一大堆,很容易就停止運行了,也算是吸取一個教訓了。最后一個就是自己的算法功底太薄弱了,代碼也寫的比較臃腫,新的學期要好好學習算法和代碼的優化方法。
更新
得老師指點,將編譯模式改為RELEASE模式,時間損耗是原來的四分之一,速度得到極大提升,因此對“效能分析與改進”板塊進行修改,用RELEASE版的截圖覆蓋了原來的DEBUG版截圖,并將RELEASE版的程序更新到GitHub上。
忽然發現如果將srand(time(0))放到遞歸函數中產生的隨機數在短時間內會相等,而采用clock()函數做種子又會造成運行兩次生成的2個文本文件有一定概率相等,然后嘗試把srand(time(0))放到主函數中只調用一次,rand()函數放到遞歸函數中調用則產生的隨機數即使在短時間內也不會相等,因此修改了隨機數產生的代碼,并將新的cpp和exe文件上傳到GitHub上。
轉載于:https://www.cnblogs.com/JorgeZhu/p/7498987.html
總結
以上是生活随笔為你收集整理的软件工程2017第二次作业的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网狐框架分析八--web登录游戏大厅流程
- 下一篇: 分布式事务参考