第二次作业——个人项目实战
本次作業的項目地址:Github倉庫地址
解題思路
嗯,這次的作業要求是
利用程序隨機構造出N個已解答的數獨棋盤
剛開始看到的時候可以說是非常茫然的,數獨以前雖然玩過,但要用程序來構造那就是完全的另一回事了。一般來說,數獨的構造是你根據已有的條件,來判斷剩下的空格該填什么數。我自己對這個沒什么研究,平時玩的時候挺隨心所欲的,這個直接導致我在面對如何用程序來解答的時候毫無頭緒。何況這次的要求根本就連條件都沒有,要自己憑空造。
憑空,還要隨機,只有第一位數是指定的,完全沒有方向,所以我就先去網上搜了“如何構建數獨”,發現不少前人對此都有研究,我在其中找到了兩篇對我來說很有用的,如下:
[http://blog.csdn.net/qq_31558353/article/details/50615760]
[http://blog.csdn.net/xiahn1a/article/details/50852849]
第一篇記錄了該如何去解一個數獨。這次作業對數獨棋盤的要求只有一個,就是左上角第一個數字為(學號后兩位相加)%9+1,我的學號末兩位為16,也就是說我的矩陣左上角那位應為8。
該篇的具體做法大致是說,從第一位開始逐個填數,如果已經有數字了就跳過,如果是空的,就從1開始看能不能填入,判斷基準是該位所在的行列和小九宮里是否有跟這個數重復的數,如果沒有,就填入這個數,如果有,就跳到下一個數判斷能不能填,以此類推。
這種做法很容易遇上某一格一個數都填不進,這個時候就返回到它的前一格,重新走下一個數判斷,直到全部填滿。
這篇代碼的作者非常厲害,僅用了一個n就可以表示數獨矩陣每一位的坐標。我因為一開始是用num[i][j]的方法在做,不想改,本身也不能完全照他的走,所以就化用了他的方法。
但即便這樣也僅僅是能構建一個以8為頭的數獨陣,并不能做到隨機。這個時候我看到了第二篇博文。這位作者的代碼有點繁雜,用到的陌生函數太多我一時看不來,但最開頭部分的他的思路給了我非常大的提示。
首先第一行肯定是1~9的一種排列,直接使用shuffle進行隨機。
當時看到這句的時候簡直是茅塞頓開。數獨陣這么多,用第一行隨機可不就是能制造大批的不一樣的數獨嘛!然而很慚愧我不會使用shuffle,而且經過查找發現這個似乎是要在1~9里隨機排列。
說到這個就要提老師的那個固定了第一位的要求了,我的首位是8,也就是說剩下8個數得在1~7和9之中隨機排列,這就很不一樣了,因為我在查“該如何生成一組隨機數”的時候,基本上都是要在一個固定范圍里隨機的,并不能像我希望的那樣可以自己指定數字。所以必須要想另外的辦法,使我可以隨心所欲的用指定的那些數隨機排序。
于是我就查找到了第三篇對我來說非常重要的博文!如下:
[http://blog.csdn.net/cxllyg/article/details/7986352]
洗牌算法,我只要自己指定好數組,就可以將[0]位后面的八個數隨機排列了。因此我定義了一個數組,內容是{8,1,2,3,4,5,6,7,9},隨機的時候將首位除外,剩下的隨機排。這也是我第一次認識rand和srand函數。
這些基本就是我的代碼的主體構成了。先是在第一個數為8的基礎下把1~9隨機排序,然后根據已有的第一行推出剩下72個空位應該填什么。
老實說這份作業做得很跌宕起伏,經常是我前一個問題還沒理清楚后腳又冒出了另一個問題。命令行輸入和查重都是最后一天才發現自己遺漏的,可以說是非常不小心非常不應該的了。何況我最后一天還要返校……命令行還好解決,查重真的就是太困難。一開始做的時候沒考慮到,后面想加也很有難度。把全部隨機出來的第一排存在同一個數組里,新隨機一個就從頭開始一個個查有沒有重復,數量少還能應付,但多了似乎就會運轉不來。之后應該會試著嘗試有沒有什么更好的方法吧。
設計實現
我一共在頭文件里定義了五個函數和一個全局變量,分別是:
bool sign=false; //用以判斷數獨陣是否完成 extern int num2[1000000][9]; //定義一個用于查重的數組void firstrank(int num[][9],int n,int sum[][9]); //隨機取數獨矩陣第一排的值 bool check(int n,int i,int j,int num[][9]); //判定數字n能否放入num[i][j]中 int build(int num[][9],int x,int y); //構建數獨矩陣 void change(); //重置sign的值為false bool diffrent(int a[][9],int b[][9],int n); //比較a,b兩個二維數組是否不相同Main函數在生成一個數獨棋盤之前,首先要先運行firstrank,定好第一行數的排列順序。
然后要運行diffrent來判斷是否有重復。每個新生成的一排隨機數都要同num2里存放前幾輪隨機過的數進行比較。一行一行比,每行一遇到不同就跳轉下一行繼續比較,如果相同,則判斷是否是該行最后一個數,如果是,則說明有完全相同的一行,返回false,如果全部比較完都沒有重復,那就返回true。
于是就可以開始運行build,從第二行首位開始構建數獨。Build中會用到check來判定這個數能否被放進這個位置里,如果check返回的值為true,就下一位;返回的值若為false,就換一個數繼續判定。當所有空位都被填上數字時,build里下一個位置就是第十行第一列,這個時候sign會被置為true,build直接return 0,表明數獨已經構建完成,可以等待輸出。
函數change是為了重復構建函數而備的,因為sign并非是在main函數里所定義,所以為了能夠重復使用,在一個數獨開始構建前,要先運行change來確保sign的值確實為false。
輸出部分因為涉及寫入文本文件,所以我直接放在main函數里了,沒有另外定義。
輸入部分要求命令行輸入,還要判斷輸入的值是否正確,我設定的是輸入錯誤就會提示“輸入錯誤,請重試”。
代碼說明
首先是firstrank的第一行隨機排序,代碼如下
int a[9]={8,1,2,3,4,5,6,7,9}; //定義一個數組,第一個數為我的學號末兩位16經計算后得出的固定值8int ran,temp;for(int i=2;i<9;i++) //數組除了第一個數固定為8,其余重組 {ran=rand()%(9-i)+i;temp=a[i-1];a[i-1]=a[ran];a[ran]=temp; }check從行,列,小九宮的角度判斷是否能夠賦值,如下:
for(a=0;a<9;a++) //判斷第i行是否有與n重復的數字 {if(num[i][a]==n) return false;}for(b=0;b<9;b++) //判斷第j列是否有與n重復的數字 {if(num[b][j]==n) return false;}if(i<3) x=0; //num[i][j]所在的小九宮左上角的行 else if(i>5) x=6;else x=3;if(j<3) y=0; //num[i][j]所在的小九宮左上角的列 else if(j>5) y=6;else y=3;for(a=x;a<x+3;a++) //判斷該九宮中是否有與n重復的數字 {for(b=y;b<y+3;b++){if(num[a][b]==n) return false;}}build從第二行第一列開始為一個一個空位賦值,全部完成后將sign的值標為true,表示數獨構造成功。
if(x==9) //當全部填滿時,數獨矩陣完成 {sign=true;return 0;}for(k=1;k<=9;k++) //從1開始逐個測試是否能放入num[x][y]的位置中 {if(check(k,x,y,num)==true){num[x][y]=k;if(y<8) build(num,x,y+1); //該位不是這一行的最末位,則繼續對其下一位進行置數 else build(num,x+1,0); //該位已經是這一行的最末位,跳至下一行首位進行置數 if(sign==true) return 0; //當數獨陣完成時,直接返回 num[x][y]=0; //如果置數失敗,則將這位置0,繼續尋找下一個適合的數字 } }Main函數先要確定隨機排序的隨機種子和數獨最后要輸出的文件“sudoku.txt”,然后根據在命令行輸入的n來構造數獨,如果輸入的不屬于規定范圍內,還要給出錯誤提示。
n=atoi(argv[argc - 1]); //命令行輸入srand((unsigned)time(NULL)); //隨機種子 ofstream outFile; //輸出文件“sudoku.txt” outFile.open("sudoku.txt");if(n<1000000 && n>1) //判斷輸入的變量是否為int型 {for(i=0;i<n;i++){change(); int num1[9][9]={0};firstrank(num1,i,num2); //確定第一行的排序if(diffrent(num1,num2,i)==1) //判斷是否重復{build(num1,1,0); //開始構建數獨for(j=0;j<9;j++) //輸出數獨陣到文件“sudoku.txt”中 {for(k=0;k<9;k++){outFile << num1[j][k] << " ";}outFile << endl;}outFile << endl; //每個數獨陣間隔一個空行 }else i=i-1;}}else{cout << "輸入錯誤,請重試。" << endl; //若變量不為int型,則輸出錯誤提示 }outFile.close();測試運行
截圖如下:
性能分析
執行力,泛泛而談的理解
執行力,按百科上的說法就是“有效利用資源、保質保量達成目標的能力”,也就是說利用現有的條件和知識,通過自己的力量又快又好的完成需要完成的任務。而泛泛而談,指浮于表面,沒有深入研究,一個人講出來的東西很膚淺毫無思考力,大概就是泛泛而談了。
按我個人的理解,拿這次作業作比,如果一個人在看到作業后立刻開始思考入手,然后在deadline來臨前從容而高水平的完成了它,那就說明他有很強的執行力了。
泛泛而談嘛,比如說,我上面提及我要換一種更穩妥的方法來查重復,不用事先設一個那么龐大的數組,我說了,卻沒有去做,并且在想起時不斷強調以后會去做,那就是一種很淺薄的表現。
……老實說,我覺得我現在在這里談論這些,談論代碼的言辭就挺膚淺挺沒有見識的……
遇到的困難及解決方法
關于代碼思路之中的困難上面已經提過了,現在說的都是一些細節上但是又很惱人的問題。
一個是命令行輸入,今天最后了才發現的,int main(int argc ,char *argv[]),乍一看很莫名其妙,好在我舍友為我傾情指點了一下,可以簡單進行使用了。
下一個是輸出到txt。這個我查了我的一本講C++的書,按照書上的說法這個創建的文檔一般會跟可執行文件在一起。于是我就照著書上的做法直接用了。我比較喜歡在編代碼的時候一個一個功能加,所以在建txt之前我都是用cout直接進行輸出的。先前的時候是另設了一個output函數來輸出,后來為了用outFile又改成直接放在main函數里。
最困難的還是沒有辦法查重復。我如果要改的話前面firstrank()就要跟著變,時間不足,所以就直接建數組。數組的話必須視線聲明大小,[1000000][9]太大了,我一開始在main函數里建的,后來發現沒辦法執行,又改成全局變量。這下不會報錯了,但我自己試了下,我的電腦最多只能隨機出5000個左右的數獨陣,如果輸入6000,就會一直停留在運行,不能結束。我猜測是電腦容量的問題。
PSP表格
| Planning | 計劃 | 10 | 30 |
| · Estimate | · 估計這個任務需要多少時間 | 10 | 30 |
| Development | 開發 | 620 | 890 |
| · Analysis | · 需求分析 (包括學習新技術) | 60 | 50 |
| · Design Spec | · 生成設計文檔 | 0 | 0 |
| · Design Review | · 設計復審 (和同事審核設計文檔) | 0 | 0 |
| · Coding Standard | · 代碼規范 (為目前的開發制定合適的規范) | 20 | 0 |
| · Design | · 具體設計 | 30 | 60 |
| · Coding | · 具體編碼 | 240 | 300 |
| · Code Review | · 代碼復審 | 30 | 60 |
| · Test | · 測試(自我測試,修改代碼,提交修改) | 240 | 420 |
| Reporting | 報告 | 90 | 90 |
| · Test Report | · 測試報告 | 60 | 70 |
| · Size Measurement | · 計算工作量 | 10 | 5 |
| · Postmortem & Process Improvement Plan | · 事后總結, 并提出過程改進計劃 | 20 | 15 |
| 合計 | 870 | 1010 |
轉載于:https://www.cnblogs.com/shadeinblue/p/7502404.html
總結
以上是生活随笔為你收集整理的第二次作业——个人项目实战的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows Mobile 开发系列文
- 下一篇: 内部链接和外部链接【转】