看完題目以後,以爲和之前作過的跳馬遍歷棋盤的題目很像,因此第一反應就是用回溯法,感受並不難,反而是沒有過的git 和GitHub讓我以爲會多花點時間,可是應該也不難吧,我當時是這樣想。 具體的解題思路,就是使用深度優先的回溯法。 如,對f()的某一次調用,若是下一個位置能放置,就遞歸調用自身,而後新調用的f()中,繼續判斷下一個位置可否放置,若是某一個位置沒有可放置的下一步,此時的數獨矩陣尚未完成的話,那麼就退出這次f(),回溯到上一層的f()中。在不斷的回溯和前進中最終獲得正確的數獨矩陣。 由於以前作過相似的題,印象也很深入,因此解決問題的技術路線並無花費多少時間,對於關鍵的函數也沒有畫流程圖,我幾乎是當即就開始建項目,準備打代碼,可是因爲題目要求代碼使用C/C++/C#,而本身已經一年沒有使用過了,這一年都在使用Java和Python,C語言這樣的基本功反而十分生疏,思路在腦中殊不知如何下手,頓時以爲十分對不起從前 的本身,<span style="color:red">從此應該不斷溫習之前學過的知識</span>,防止再出現這樣的窘境
數據結構
翻出了之前寫的跳馬遍歷棋盤的代碼,看了一遍,對C語言也不似剛開始生疏,對解決問題內心有了底,便立刻投入編代碼的過程當中。
----------------------------------------------------------------------------------------------------------------html
printSudo() 向文件輸出完整的數獨矩陣
在函數fillform()中調用set(),聯合判斷下一步的動做,因爲是基於深度優先的思想,一旦判斷到具備可走的下一步,就當即執行。實際代碼以下:ios
bool fillFrom(int y, int val){ int xOrd[9]; initXOrd(xOrd); for (int i = 0; i<9; i++){ int x = xOrd[i]; //做業要求 if (val == 7 && x == 0 && y == 0) { if (fillFrom(y + 1, val))//直接下一行繼續填數 return true; else { continue; } } if (set(x, y, val)){ if (y == 8)//到了最後一行 { if (val == 9 || fillFrom(0, val + 1))//當前填9則結束, 不然從第一行填下一個數 return true; } else{ if (fillFrom(y + 1, val))//下一行繼續填當前數 return true; } //回溯 reset(x, y, val); } } return false; }
實際設計代碼遇到的第一個問題就是將數字以怎樣的順序填入表格中,最原始的想法就是將1到9按序填入,填完一組1到9,再填入下一組1到9,可是這樣的執行會致使回溯率很高,程序的性能收到嚴重限制。爲了找到更適合的算法,我最終在[一篇新浪博客](http://blog.sina.com.cn/s/blog_a28e3dd90101e1i2.html)中收到了啓發,文章經過使用分治法將相同的數字統一填入,填徹底部的1以後,再填所有的2,這樣有效地減小了回溯的次數,提高了系統運行的速度。 新的思路是經過按行放置的順序,放置完全部的1,而後放置2,以此不斷推算下去。這樣作還有一個額外的好處就是在檢查某一點放置是否符合規則時,能夠省去行衝突檢查的時間。 由於使用了按行放置的方法,那麼爲了造成多樣的數獨矩陣,只能在列上面作文章。現有的辦法是隨機生成每個數字放置的列順序,經過InitXord()函數實現,具體代碼以下。經過生成隨機數,隨機打亂X的順序,以此作到生成不一樣的數獨矩陣。
void initXOrd(int* xOrd)//0~8隨機序列 { int i, k, tmp; for (i = 0; i<9; i++) { xOrd[i] = i; } for (i = 0; i<9; i++) { k = rand() % 9; tmp = xOrd[k]; xOrd[k] = xOrd[i]; xOrd[i] = tmp; } }
解決了放置的順序以後,就是set()函數,這個函數的做用就是判斷某一點是否能夠放置,每次放置都要調用set()函數,做用可想而知,所以這個函數也極大影響程序的性能。
在此複習一下數獨的規則,簡單來講就是將數字0到9填入99的表格中,而且每行裏的數字不能重複,列中的數字不能重複,在9個33的九宮格里的數字也不能重複。set()函數的做用就是檢查放置的數是否違反了規則。
最簡單的檢查辦法就是對列和小九宮格進行遍歷,鑑於矩陣很小(9*9),這樣的實現是具備必定可行性的,可是否有更快的方法呢?
我在本次程序中使用的方法依然基於分治法,整體的思路爲:開闢一個列檢查矩陣checkCol[9][9]和九宮格檢查矩陣checkBox[9][9],在列檢查矩陣中,checkCol[i][j]映射爲第i列的數字 j 若是checkCol[i][j]爲0,表示在這一列中數字j還未存放過,也就不存在重複,能夠放置,將其賦值爲1;若是爲1,則表示第 i 列中的數字 j 已經存在,不能繼續放置數字 j 。一樣的想法應用於checkBox[9][9],將九個小九宮格編號,checkBox[i][j] 映射爲第 i 個九宮格的數字 j,若是爲0表示爲放置,1表示已放置。這樣將set()的時間複雜度減小到n(1)。下面爲set()函數代碼:git
bool set(int x, int y, int val) { if (sudo[y][x] != 0)//非空 return false; if (checkCol[x][val - 1] == 1) return false; int y0 = y / 3; int x0 = x / 3; int i = y0 * 3 + x0; if (checkBox[i][val - 1] == 1) return false; checkCol[x][val - 1] = 1; checkBox[i][val - 1] = 1; sudo[y][x] = val; //printSudo(); return true; }
另外爲了知足老師做業要求中對左上角數字的固定,在初始化矩陣時將數字寫入:github
void init() { for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { sudo[i][j] = 0; checkCol[i][j] = 0; checkBox[i][j] = 0; } } //做業要求: sudo[0][0] = 7; checkCol[0][6] = 1; checkBox[0][6] = 1; }
最後一步將完成的矩陣輸出到sudoku.txt中,使用
try { out.open("sudoku.txt", ios::trunc); } catch (exception e) { cout << "打開文件:sudoku.txt 失敗!!"; }
經過複寫 '' << "方法,將矩陣輸出到文件中。算法
運行後輸入要輸出的矩陣的個數
結果
數據結構
這是在mac pro上運行的性能分析(輸出100萬個矩陣)
函數
這是在本身的電腦上運行debug的性能分析(輸出100萬個矩陣)
性能
運行release
學習
程序中消耗最大的函數爲fillForm(),這並不出意外,但令我奇怪的是輸出函數也佔了那麼大的比重,達到了近40%的比重,由於1000000次的IO太慢了,接下來思考如何加快矩陣寫入文件的速度。測試
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | ||
· Estimate | · 估計這個任務須要多少時間 | 8*60 | 10*60 |
Development | 開發 | ||
· Analysis | · 需求分析 (包括學習新技術) | 1*60 | 3*60 |
· Design Spec | · 生成設計文檔 | 30 | 30 |
· Design Review | · 設計複審 (和同事審覈設計文檔) | 5 | 5 |
· Coding Standard | · 代碼規範 (爲目前的開發制定合適的規範) | 5 | 10 |
· Design | · 具體設計 | 0 | 0 |
· Coding | · 具體編碼 | 1.5*60 | 1.5*60 |
· Code Review | · 代碼複審 | 30 | 10 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 30 | 2*60 |
Reporting | 報告 | ||
· Test Report | · 測試報告 | 60 | 2*60 |
· Size Measurement | · 計算工做量 | 10 | 5 |
· Postmortem & Process Improvement Plan | · 過後總結, 並提出過程改進計劃 | 30 | 30 |
合計 | 330 | 540 |