課程:北航-2020-春-軟件工程 | 博客園班級博客 |
---|---|
項目 | 內容 |
要求:求交點個數 | 我的項目做業 |
班級:005 | Sample |
GitHub地址 | IntersectProject |
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | ||
· Estimate | · 估計這個任務須要多少時間 | 10 | 10 |
Development | 開發 | ||
· Analysis | · 需求分析 (包括學習新技術) | 60 | 120 |
· Design Spec | · 生成設計文檔 | 10 | 10 |
· Design Review | · 設計複審 (和同事審覈設計文檔) | 0 | 0 |
· Coding Standard | · 代碼規範 (爲目前的開發制定合適的規範) | 20 | 0 |
· Design | · 具體設計 | 20 | 20 |
· Coding | · 具體編碼 | 60 | 120 |
· Code Review | · 代碼複審 | 20 | 30 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 60 | 60 |
Reporting | 報告 | ||
· Test Report | · 測試報告 | 30 | 30 |
· Size Measurement | · 計算工做量 | 20 | 10 |
· Postmortem & Process Improvement Plan | · 過後總結, 並提出過程改進計劃 | 60 | 60 |
合計 | 370 | 470 |
可見本身對工做量的估算與實際狀況相差較大,緣由主要在於新技術的學習上。因爲過去使用c++的頻率比較低,所以此次項目正式開工前把大量的時間花在了學習c++上.c++
解題思路描述。即剛開始拿到題目後,如何思考,如何找資料的過程。(3')git
設計實現過程。設計包括代碼如何組織,好比會有幾個類,幾個函數,他們之間關係如何,關鍵函數是否須要畫出流程圖?單元測試是怎麼設計的?(4')github
代碼說明。展現出項目關鍵代碼,並解釋思路與註釋說明。(3')算法
通過思考這道題彷佛並無更好的辦法,惟一的辦法就是算出全部的交點而且去重。因爲已經算過的圖形不必再算一遍,算法複雜度爲O(n(n-1))=O(n^2).編程
對直線的表示使用y=kx+b的表示方法,基於2點考慮。第一這樣寫表示方法惟一,同時方便與圓計算求交點。第二這樣寫可以方便對於平行線的斷定。但須要考慮一種特殊的直線x=t,對這樣的直線採起特殊狀況處理安全
全部交點共分爲3種狀況,共包括直線與直線的交點,直線與圓的交點以及圓與圓的交點,對每種狀況採起單獨的函數進行計算處理。函數
首先設計Shape類做爲全部圖形的基類,以後設計Line類和Circle類,以及Point類.直線使用y=kx+b表示,圓使用(x-a)2+(y-b)2=r^2表示,具體實現以下所示性能
class Shape { public:string mtype; Shape(string i_type) { mtype = i_type; } }; class Line :public Shape { public:double ma, mb; int mspecial;//常規直線mspecial=0,垂直於X軸的直線mspecial=1,ma爲X軸對應值 Line() :Shape("L") { ma = 0; mb = 0; mspecial = 0; } Line(double input1, double input2, int ifspecial) :Shape("L") { ma = input1; mb = input2; mspecial = ifspecial; } }; class Circle :public Shape { public:double mx, my, mr; Circle() :Shape("C") { mx = 0; my = 0; mr = 0; } Circle(double input1, double input2, double input3) :Shape("C") { mx = input1; my = input2; mr = input3; } } class Point { public:double x, y; Point(double input1, double input2) { x = input1; y = input2; } };
須要注意的是對於point類,因爲x,y都是double類型。在hash判斷時有很小的機率會發生錯誤,但不能忽略,所以重寫了它的Hash函數以及equal函數,以下所示。單元測試
struct Hash_Point { size_t operator()(const class Point& input1)const { //return (int)(((int)input1.x) * 1e6 / 10 + ((int)input1.y) * 1e6 / 10); long temp1 = (long)input1.x; long temp2 = (long)input1.y; return (temp1 + temp2) * 13 + (temp1 * 1000 % 1000 + temp2 * 1000 % 1000); } }; struct Equal_Point { bool operator()(const class Point& input1, const class Point& input2)const { return abs(input1.x - input2.x) <= EPS && abs(input1.y - input2.y) <= EPS; //return input1.x == input2.x && input1.y == input2.y; } };
採用了EPS規避計算致使double產生的較小偏差,實際運行效率也仍是不錯的。學習
單元測試分爲3類,第一類是對於部分函數的測試,包括圓與直線是否存在交點,以及圓與圓之間是否存在焦點的判斷。
第二類包括各類特殊狀況,包括圓與圓外切,圓與圓內切以及直線與圓外切,直線與直線平行,多條線交於同一點,多個圓交於同一點的狀況。
第三類使用隨機產生的圓和直線進行測試,觀察是否知足時間要求。
第一次測試時以下圖所示,delete函數佔據了很是大的運算部分。猜想多是參數傳遞時致使重複拷貝複製並刪除形成,所以在接下來對其進行優化。
下圖爲優化後的結果,將全部實例傳遞所有改成指針傳遞,可見new代替了delete,可是所佔比例大幅降低。
因爲全部節點集使用unordered_set進行儲存,所以hash函數使用了很是高頻次。我對hash函數進行了優化,使得hash所需時間有必定幅度的減小.
最爲關鍵的代碼是求交點部分的代碼,具體以下所示。在具體代碼計算以前先對直線是否平行進行判斷,以及圓與直線距離是否大於圓的半徑以及圓與圓是否互相包含或者圓與圓是否不相交,以後在計算具體交點時須要判斷被除數爲0的狀況,具體代碼以下所示:
void L2L(Line* input1, Line* input2, unordered_set<Point, Hash_Point, Equal_Point>& g_allpoint) { if (input1->mspecial == 0 && input2->mspecial == 0) { if (abs(input1->ma - input2->ma) <= EPS) { return; } double x = (input2->mb - input1->mb) / (input1->ma - input2->ma); double y = x * input1->ma + input1->mb; g_allpoint.insert(Point(x, y)); return; } else if (input1->mspecial == 1 && input2->mspecial == 1) return; else { if (input1->mspecial == 1) { g_allpoint.insert(Point(input1->ma, input1->ma * input2->ma + input2->mb)); } else { g_allpoint.insert(Point(input2->ma, input1->ma * input2->ma + input1->mb)); } } } void C2C(Circle* input1, Circle* input2, unordered_set<Point, Hash_Point, Equal_Point>& g_allpoint) { double r2 = input2->mr, r1 = input1->mr, a1 = input1->mx, a2 = input2->mx, b1 = input1->my, b2 = input2->my; double x1 = 0, y1 = 0, x2 = 0, y2 = 0; if (abs(a1 - a2) < EPS && abs(b1 - b2) < EPS) return; else if (!Circle::C2DisJudge(input1, input2)) return; else if (abs(b1 - b2) < EPS) { x1 = (r2 * r2 - r1 * r1 + a1 * a1 - a2 * a2) / (2 * a1 - 2 * a2); y1 = sqrt(r1 * r1 - (x1 - a1) * (x1 - a1)) + b1; y2 = -sqrt(r1 * r1 - (x1 - a1) * (x1 - a1)) + b1; g_allpoint.insert(Point(x1, y1)); g_allpoint.insert(Point(x1, y2)); return; } else { double tempm = (r2 * r2 - r1 * r1 + a1 * a1 - a2 * a2 + b1 * b1 - b2 * b2) / (2 * b1 - 2 * b2); double tempk = -(2 * a1 - 2 * a2) / (2 * b1 - 2 * b2); double temp1 = (1 + tempk * tempk); double temp2 = 2 * tempk * (tempm - b1) - 2 * a1; double temp3 = a1 * a1 + (tempm - b1) * (tempm - b1) - r1 * r1; x1 = (-temp2 + sqrt(temp2 * temp2 - 4 * temp1 * temp3)) / (2 * temp1); x2 = (-temp2 - sqrt(temp2 * temp2 - 4 * temp1 * temp3)) / (2 * temp1); y1 = tempk * x1 + tempm; y2 = tempk * x2 + tempm; g_allpoint.insert(Point(x1, y1)); g_allpoint.insert(Point(x2, y2)); } return; } void C2L(Circle* input1, Line* input2, unordered_set<Point, Hash_Point, Equal_Point>& g_allpoint) { if (!Circle::CLDisJudge(input1, input2)) return; double x1 = 0, x2 = 0, y1 = 0, y2 = 0; double r1 = input1->mr, a1 = input1->mx, b1 = input1->my; if (input2->mspecial == 1) { x1 = input2->ma; y1 = sqrt(r1 * r1 - (x1 - a1) * (x1 - a1)) + b1; y2 = -sqrt(r1 * r1 - (x1 - a1) * (x1 - a1)) + b1; g_allpoint.insert(Point(x1, y1)); g_allpoint.insert(Point(x1, y2)); return; } else { double tempm = input2->mb; double tempk = input2->ma; double temp1 = (1 + tempk * tempk); double temp2 = 2 * tempk * (tempm - b1) - 2 * a1; double temp3 = a1 * a1 + (tempm - b1) * (tempm - b1) - r1 * r1; x1 = (-temp2 + sqrt(temp2 * temp2 - 4 * temp1 * temp3)) / (2 * temp1); x2 = (-temp2 - sqrt(temp2 * temp2 - 4 * temp1 * temp3)) / (2 * temp1); y1 = tempk * x1 + tempm; y2 = tempk * x2 + tempm; g_allpoint.insert(Point(x1, y1)); g_allpoint.insert(Point(x2, y2)); } }
這部分大量計算可能產生必定的偏差,但大機率不會超過EPS規定的偏差範圍。
須要處理的問題大多在於_s函數的使用。因爲visual studio彷佛對編程安全性要求較高,所以大量函數必須採用__s安全形式。