軟件工程基礎 第3次我的做業

一點說明

這篇博客是軟件工程基礎(羅傑、任建)的第三次課程做業(我的項目做業)git

項目 內容
這個做業屬於哪一個課程 軟件工程基礎(羅傑,任建)
這個做業的要求在哪裏 做業要求的連接
我在這個課程的目標是 提高對軟件工程的宏觀和微觀的全面認識,並加以實踐
做業在哪些方面幫我實現目標 親身實踐我的項目開發的完整流程
個人教學班級 006
個人GitHub項目地址 https://github.com/SnowOnVolcano/IntersectProject.git

PSP表格

在開始實現程序以前,在下述 PSP 表格記錄下你估計將在程序的各個模塊的開發上耗費的時間。(0.5')github

在你實現完程序以後,在下述 PSP 表格記錄下你在程序的各個模塊上實際花費的時間。(0.5')算法

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 15 20
· Estimate · 估計這個任務須要多少時間 15 20
Development 開發 330 470
· Analysis · 需求分析 (包括學習新技術) 60 80
· Design Spec · 生成設計文檔 30 20
· Design Review · 設計複審 (和同事審覈設計文檔) 0 0
· Coding Standard · 代碼規範 (爲目前的開發制定合適的規範) 10 10
· Design · 具體設計 40 60
· Coding · 具體編碼 100 120
· Test · 測試(自我測試,修改代碼,提交修改 90 180
Reporting 報告 75 110
· Test Report · 測試報告 45 60
· Size Measurement · 計算工做量 10 10
· Postmortem & Process Improvement Plan · 過後總結, 並提出過程改進計劃 20 40
Total 合計 420 600

基本需求

1. 解題思路

解題思路描述。即剛開始拿到題目後,如何思考,如何找資料的過程。(3')函數

  • 思路一

    讀完問題的第一思路,是利用高中學到的 通常方程法 進行交點的求解:工具

    \[設直線方程爲\ ax+by+c=0,\ 已知直線過兩點(x_1,y_1)和(x_2,y_2),則有\\ \begin{cases}ax_1+by_1+c=0\\ax_2+by_2+c=0\end{cases}\implies \begin{cases}a=y_1-y_2\\b=x_2-x_1\\c=x_1y_2-x_2y_1\end{cases}\\ 設兩直線分別爲\ a_1x+b_1y+c_1=0\ 和\ a_2x+b_2y+c_2=0,聯立兩直線方程,得\\ \begin{cases}a_1x+b_1y+c_1=0\\a_2x+b_2y+c_2=0\end{cases}\implies \begin{cases}x=\frac{b_1c_2-b_2c_1}{a_1b_2-a_2b_1}\\y=\frac{a_2c_1-a_1c_2}{a_1b_2-a_2b_1}\end{cases}\implies \begin{cases}x=\frac{b_1c_2-b_2c_1}{D}\\y=\frac{a_2c_1-a_1c_2}{D}\end{cases},其中\ D=a_1b_2-a_2b_1 \]

    這樣的解法有幾個好處:性能

    • 能夠較好地處理特殊狀況,好比,能夠經過判斷 D 是否爲 0,直接判斷兩直線是否平行;
    • 這種方法直接計算出交點的座標,這樣能夠設置一個 set 用來存放已獲得的交點,直接在存入時進行除重,最終結果輸出 set 的大小便可;

    固然,其缺點也很明顯:單元測試

    • 計算簡單粗暴,對於具備 N 條直線的樣本,須要進行 \(C_N^2\) 次計算,時間複雜度爲 \(O(n^2)\)
    • 若是簡單地使用 set<(x,y)> 進行的方式進行存儲,則須要進行浮點運算,這既會帶來精度的損失,也會加長運行時間。
  • 思路二

    若是不採用直接計算的方法,如何得出交點個數呢?我想到的另外一個方法是,作減法學習

    若是任意的兩條直線都相交且任意的三條直線不交於同一點,那麼對於具備 N 條直線的樣本,交點的個數爲 \(C_N^2\),對通常狀況就有,測試

    \[總交點數=C_N^2-平行直線對的數目-\sum_{全部的交點}{(同一交點的直線數目-2)} \]

    可是,仔細想一想,這種方法彷佛並不比思路簡單,由於我沒有想到好的算法去計算同一交點的直線數目……優化

  • 思路三

    想不到好的算法,我最後只能決定使用直接計算的方法,可是我想其實思路一的方法還能夠簡單地優化一下細節,減少精度損失,有兩個方法,

    • 在存放交點時,交點的縱橫座標的分子分母分開存儲,這樣能夠規避浮點運算,從而保證精度;
    • set 的排序函數進行重載,設置必定的精度範圍,這樣能夠必定程度地減少精度損失,可是沒法從根本上避免。

    我最終選擇了後者,以平衡精度和時間複雜度。

2. 實現過程

設計實現過程。設計包括代碼如何組織,好比會有幾個類,幾個函數,他們之間關係如何,關鍵函數是否須要畫出流程圖?單元測試是怎麼設計的?(4')

  • 結構設計(2個結構體)
    • Point:表示點 \((x,y)\),其中 Point.xPoint.y 分別表示點的縱橫座標;
    • Line:表示直線 \(ax+by+c=0\),其中 Line.aLine.bLine.c 分別對應直線的三個參數;
  • 函數實現(2+1+1個函數)
    • PointLine 的構造函數,計算兩直線交點的函數 calLineLineIst(...) ,主函數;

    • 各函數關係以下,

      img
  • 單元測試

    我主要進行了三個方面的單元測試:

    • 構造函數是否能正確初始化:包括參數的傳遞和計算;
    • 交點計算函數是否能覆蓋全部狀況:包括多線一點、平行的狀況,以及直線與座標軸平行等狀況;
    • 交點集合的精確度是否能保證:重點測試瞭如下兩種狀況,1)在交點相差較小的狀況下,是否可以區分開不一樣的交點;2)在交點的小數點位數較多時,是否能保證交點不重複。

    如下是個人一些單元測試的測試點的圖形示意:

3. 性能改進

記錄在改進程序性能上所花費的時間,描述你改進的思路,並展現一張性能分析圖(由 VS 2019 的性能分析工具自動生成),並展現你程序中消耗最大的函數。(3')

  • 在改進以前,我進行了一次程序性能的測試,我發現,最費時的操做是 set 的插入時,遍歷紅黑樹的過程。反而計算交點的函數沒有花費太多的時間。
  • 可是,在嘗試了非紅黑樹的集合以後,我發現有時候正確性得不到保證,因此我最後仍是選擇了保持原有的 set。
  • 其餘的優化都是小修小補了,好比,
    • 優化了代碼結構,將點和直線的初始化函數放入告終構體內;
    • 除了最後的計算,中間過程使用整型,而非浮點型
  • 優化後的圖以下
img
  • 程序中消耗最大的函數以下
img

4. 代碼說明

代碼說明。展現出項目關鍵代碼,並解釋思路與註釋說明。(3')

  • 計算直線與直線交點(思路請見代碼註釋)
    // calculate the intersections of two lines
    static void calLineLineIst(Line& line1, Line& line2) {
    	int D;
        D = line1.a * line2.b - line2.a * line1.b;
    	switch (D)
    	{
    	case 0:	// parallel
    		break;
    	default:
    		//	line1: a1*x+b1*x+c1=0, line2: a2*x+b2*x+c2=0
    		//	==> x=(b1*c2-b2*c1)/(a1*b2-a2*b1),
    		//		y=(a2*c1-a1*c2)/(a1*b2-a2*b1)
    		//	let D=a1*b2-a2*b1
    		//	==> x=(b1*c2-b2*c1)/D, y=(a2*c1-a1*c2)/D
    		Point point = { 
    			(line1.b * line2.c - line2.b * line1.c) / (float)D, 
    			(line2.a * line1.c - line1.a * line2.c) / (float)D 
    		};
    		points.insert(point);
    		break;
    	}
    }
  • 集合的排序和精度的肯定

    bool operator == (const Point& other) const {
    	return fabs(x - other.x) < 0.00000001 && fabs(y - other.y) < 0.00000001;
    	}
    bool operator < (const Point& other) const {
    	if (x != other.x) {
    		return x < other.x;
    	}
    	else {
    		return y < other.y;
    	}
    }

附加題

1. 解題思路

\[已知兩圓\ (x-x_1)^2+(y-y_1)^2=(r_1)^2\ 和\ (x-x_2)^2+(y-y_2)^2=(r_2)^2\ ,\\設圓心距爲\ d,則有\ \ \ \begin{cases}兩圓內含或相離, &d<|r_1-r_2|\ 或\ d>r_1+r_2\\兩圓相交或相切,&|r_1-r_2|\leq d\leq r_1+r_2\end{cases}\\\begin{cases}(x-x_1)^2+(y-y_1)^2=(r_1)^2\\(x-x_2)^2+(y-y_2)^2=(r_2)^2\end{cases}\implies 2(x_2-x_1)x+2(y_2-y_1)y+x_1^2-x_2^2+y_1^2-y_2^2+r_2^2-r_1^2\\\implies \begin{cases}(x-x_1)^2+(y-y_1)^2=(r_1)^2\\ 2(x_2-x_1)x+2(y_2-y_1)y+x_1^2-x_2^2+y_1^2-y_2^2+r_2^2-r_1^2\end{cases}\implies 求出交點座標 \]

2. 代碼說明

  • 計算直線與圓交點(思路請見代碼註釋)
    // calculate the intersections of line and Circle
    static void calLineCircleIst(Line& line, Circle& circle) {
    	int intercept;
    	// intercept=r^2-d^2=r^2-(ax+by+c)^2/(a^2+b^2)
    	intercept = (int)(pow(circle.r, 2) - pow(line.a * circle.x + line.b * circle.y + line.c, 2) / (pow(line.a, 2) + pow(line.b, 2)));
    	// not intersect
    	if (intercept < 0) { return; }
    	// tLine is perpendicular to line
    	Line tLine = { line.b, -line.a, line.a * circle.y - line.b * circle.x };
    	int D;
    	D = tLine.a * line.b - line.a * tLine.b;
    	// tPoint is the intersection of line and tLine
    	Point tPoint = {
    		(tLine.b * line.c - line.b * tLine.c) / (float)D,
    		(line.a * tLine.c - tLine.a* + line.c) / (float)D
    	};
    	switch (intercept) 
    	{
    	case 0:	// line is tangent to circle
    		points.insert(tPoint);
    		break;
    	default:// line passes through circle
    		float vecX;
    		float vecY;
    		float offset;
    		// (vecX, vecY) is a unit vector
    		vecX = (float)(line.b / sqrt(pow(line.a, 2) + pow(line.b, 2)));
    		vecY = (float)(-line.a / sqrt(pow(line.a, 2) + pow(line.b, 2)));	
    		// Offset is half of the intercept
    		offset = (float)sqrt(intercept / (pow(line.a, 2) + pow(line.b, 2)));
    		// intersection = tPoint +/- vec*offset
    		Point ist1 = { tPoint.x + vecX * offset, tPoint.y + vecY * offset };
    		Point ist2 = { tPoint.x - vecX * offset, tPoint.y - vecY * offset };
    		points.insert(ist1);
    		points.insert(ist2);
    		break;
    	}
    }
  • 計算圓與圓交點(思路請見代碼註釋)
    // calculate intersections of two circles
    static void calCircleCircleIst(Circle& circle1, Circle& circle2) {
    	int radiusSum;
    	int radiusDiff;
    	int centerDis;
    	radiusSum = (int)pow(circle1.r + circle2.r, 2);
    	radiusDiff = (int)pow(circle1.r - circle2.r, 2);
    	centerDis = (int)(pow(circle1.x - circle2.x, 2) + pow(circle1.y - circle2.y, 2));
    	// not intersect
    	if (centerDis > radiusSum || centerDis < radiusDiff) {
    		return;
    	}
    	// line passes both two intersections of circles
    	Line line = {
    		circle1.d - circle2.d, 
    		circle1.e - circle2.e,
    		circle1.f - circle2.f
    	};
    	// the intersections of two circles are also the intersections of line and circle
    	calLineCircleIst(line, circle1);
    }

截圖 - 測試與度量

1. 單元測試覆蓋率

img

2. Code Quality Analysis 警告消除

img
相關文章
相關標籤/搜索