項目 | 內容 |
---|---|
這個做業屬於哪一個課程 | 2020春季計算機學院軟件工程(羅傑 任健) (北京航空航天大學 - 計算機學院) |
這個做業的要求在哪裏 | 我的項目做業 |
個人教學班級 | 005 |
這個項目的GitHub地址 | https://github.com/LastWhisper1/IntersectionCounter |
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | ||
· Estimate | · 估計這個任務須要多少時間 | 10 | 5 |
Development | 開發 | ||
· Analysis | · 需求分析 (包括學習新技術) | 240 | 180 |
· Design Spec | · 生成設計文檔 | 90 | 60 |
· Design Review | · 設計複審 (和同事審覈設計文檔) | 30 | 30 |
· Coding Standard | · 代碼規範 (爲目前的開發制定合適的規範) | 10 | 10 |
· Design | · 具體設計 | 60 | 90 |
· Coding | · 具體編碼 | 90 | 90 |
· Code Review | · 代碼複審 | 20 | 20 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 90 | 180 |
Reporting | 報告 | ||
· Test Report | · 測試報告 | 30 | 30 |
· Size Measurement | · 計算工做量 | 30 | 30 |
· Postmortem & Process Improvement Plan | · 過後總結, 並提出過程改進計劃 | 30 | 30 |
合計 | 730 | 755 |
從實際結果來看,整個項目的實現時間仍是太長了。我碼代碼的能力果真仍是使人捉急,雖然前期有過很複雜的想法,但在具體編碼時並無獲得實現,對最後的程序性能也不太滿意。雖然不熟悉C++能夠做爲一部分緣由,但總的來講在效率上仍是有很大的提高空間,從此仍是要增強本身的編碼能力,避免有想法而實現不了的狀況。c++
爲了不各類偏差對結果形成的影響,解題中避免了使用浮點數,具體的實現方式上,使用瞭如下三個類:git
直線採用最萬能的標準式 $$ Ax+By+C=0 $$ 來保存,經過給定的兩點計算出三個參數便可,而且無需化簡。github
這個類中還實現了檢驗兩條直線是否平行的函數,因爲不考慮重合,只需對兩條直線 $$ \left{\begin{matrix} & A_{1}x + B_{1}y + C_{1}=0\ & A_{2}x + B_{2}y + C_{2}=0 \end{matrix}\right. $$ 驗證是否成立 $$ A_{1}B_{2}=A_{2}B_{1} $$ 便可。框架
題目要求已經確保直線不會重合,經過函數驗證兩條直線不平行後,方程 $$ \left{\begin{matrix} & A_{1}x + B_{1}y + C_{1}=0\ & A_{2}x + B_{2}y + C_{2}=0 \end{matrix}\right. $$ 的惟一解是 $$ \left{\begin{matrix} x = \frac{C{{1}}B{{2}}-C_{2}B_{1}}{B{{1}}A{{2}}-B_{2}A_{1}}\ y = \frac{A{{1}}C{{2}}-A_{2}C_{1}}{B{{1}}A{{2}}-B_{2}A_{1}} \end{matrix}\right. $$函數
在保存時,分別保存$x, y$的分子和分母(都是整數),這樣就避免了浮點數致使的精度問題。若是接下去考慮把圓加進來的話,能夠再額外保存根式下的分子和分母。但程序中沒有實現圓相關的部分,故再也不贅述。性能
每讀入一條直線便與每一條現有直線比較,若兩條直線不平行,則計算交點值並放入set容器中,最後將新讀入的直線放入vector容器中。這樣作的話,若是使用unordered_set,在平均狀況下的時間複雜度爲$O(n^{2})$,若使用set則爲$O(n^{2}logn)$。單元測試
在set與unordered_set的選擇中,發現程序在輸入規模 $N = 10000$ 時,使用set會致使超過60秒時間限制,但使用unorder_set會致使bad_alloc異常。通過權衡,既然想不到更好的方法提高性能,不如使用set儘量保證準確性,也能避免使用unorder_set後因hash函數致使的bug。學習
程序中,存儲和計算交點是必不可少的工做量。對每一個交點,若是在計算中,發現直線經過某個交點,那麼該直線和經過這個交點的全部直線,都不可能再有其餘交點,這樣就能夠省去和一些直線計算交點的步驟。考慮到性能測試中交點個數$h$遠小於$N(N-1)/2$,暗示多線共點的狀況較多,那麼這種方法能起到比較大的優化做用。測試
針對平行線的優化可能收效不大,舉個例子,若一條直線與一組$m$條平行線中的某一條相交,能夠知道該直線與該組平行線共產生了$m$個交點,但因爲存在多線共點的狀況,仍須要計算每一個交點的位置。優化
如上一段所說,我實現了Line,Intersection,Counter三個類,並採用了上面所說的容器。在命令行參數的識別上,採用比較普通的while循環來實現參數的讀取和處理。下面主要講一下使用的測試方法與測試樣例的構建。
在構建的單元測試樣例中,我作了基本的功能測試和邊界測試。在基本的功能測試中,我儘量測試了每一個函數的功能是否正確,包括直線標準式是否正確、交點計算是否正確(也就是Line和Intersection的構造函數),同時構建了幾個比較基礎的樣例測試。在邊界測試中,考慮了諸如邊界點肯定直線、兩條直線的交點很是接近的狀況進行樣例構建,測試結果並無問題。下面這張圖描述了其中一個測試樣例的構建:
在性能方面的壓力測試中,我另寫了一個cpp文件(代碼不在git倉庫中),使用隨機生成的方法構建大量樣例。雖然這樣生成的直線並不能保證不重合(實際上機率很是小),但在我本身的程序邏輯中,兩條直線重合被視爲平行,不會引起崩潰性的bug,故在性能測試中能夠接受。實際上,隨機生成的直線幾乎不存在平行或重合的狀況,得出的交點數基本都是$N(N-1)/2$。
程序寫好之後,實際上並無作大的框架上的改動,但在細節處理上仍是改過不少地方。例如,判斷兩條直線是否平行的函數和Counter類中插入新直線的函數,因爲函數體較爲簡短,故改爲了inline函數提升性能。另外,程序中還嘗試使用double代替以前介紹的分數表示方法來存儲交點,實際顯示性能提高不是那麼明顯。以下是在使用set容器存儲交點集合,$N = 5000$時性能測試的截圖:
從截圖中能夠看出,程序運行時間中至關一部分都在set的RB樹構建上(固然以前測試的時候也對其餘時間佔比較大的地方進行了優化)。
若把set替換成unordered_set的話,性能會有比較明顯的提高(經測試,在相同$N$值下,平都可減小$1/3$執行時間),但因爲沒辦法很好解決掉內存分配異常的問題,加上hash函數可能存在錯誤,故仍是採用set容器儘量保證程序正確性。
首先展現交點的構造函數,這裏的A1, B2等使用了宏定義,對應了兩條直線標準式的相應參數,具體含義以前的部分已經說起:
Intersection::Intersection(Line* line1, Line* line2) { int xnume = C1 * B2 - C2 * B1; int xdeno = B1 * A2 - B2 * A1; int ynume = A1 * C2 - A2 * C1; int ydeno = xdeno; }
代碼中的xnume對應交點x座標的分子,xdeno對應x的分母,y座標依此類推。經過分子分母分別存儲的方法,避免了浮點數可能帶來的精度問題。
下面是整個程序的核心函數CountIntersections:
int Counter::CountIntersections() { for (size_t i = 0; i < lineSet->size(); i++) { Line* line1 = lineSet->at(i); for (size_t j = 0; j < i; j++) { Line* line2 = lineSet->at(j); if (!line1->isParallel(line2)) { Intersection intsec(line1, line2); intersectionSet->insert(intsec); } } } return intersectionSet->size(); }
代碼的邏輯在上文中已經介紹過了,這段函數中的isParallel()
爲平行線的斷定函數,並寫成了內聯函數以提升性能。若想要繼續提升這個函數的性能,除了將set改爲unordered_set以外,只能從循環條件出發,略去一些沒必要要的交點計算。
還請老師助教提出批評意見。
3/10追記:在和結對搭檔對拍程序的時候,發現了比較嚴重的bug,初步判斷是Intersection類中運算符重載錯誤引發的,但短期內難以找出消除bug的方法。在以前和搭檔對double精度的討論中,同伴的觀點是double精度足夠完成實驗需求,因此在最後一次提交時,利用程序中預留的宏定義開關將Intersection類改成double實現,直接存儲交點x, y座標的double值(在博客代碼的基礎上作除法便可獲得)。