个人项目作业(1)

项目 内容
这个作业属于哪个课程 2020计算机学院软件工程(罗杰 任健)
这个作业的要求在哪里 个人项目作业
教学班级 005
项目地址 个人项目作业

PSP 2.1表格

PSP 2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 60
· Estimate · 估计这个任务需要多少时间 480 730
Development 开发 520 570
· Analysis · 需求分析 (包括学习新技术) 30 60
· Design Spec · 生成设计文档 30 60
· Design Review · 设计复审 (和同事审核设计文档) 0 0
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 0 0
· Design · 具体设计 60 90
· Coding · 具体编码 180 240
· Code Review · 代码复审 0 0
· Test · 测试(自我测试,修改代码,提交修改) 120 180
Reporting 报告 70 100
· Test Report · 测试报告 30 60
· Size Measurement · 计算工作量 10 10
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 30
合计 520 730

解题思路

题目给的直线的参数是两个点,首先选择了直线的公式Ax+By+C=0而不是使用y = kx + b, 可以排除直线垂直于y轴时k不存在的判断条件。进一步决定所需要保存的一些参数。
为了保证不遗漏不重复,任意两个图形之间需要比较一次,这是\(O(n^2)\)的时间复杂度,没有找到有效的消除复杂度的方式,所以优化的核心就是尽可能的剪枝和高效的计算交点。直线与直线之间的交点没有很好的优化方法,联立表达式求解。而直线和圆以及圆和圆之间交点参照这篇文章的算法计算。然后就是剪枝办法,一旦有圆参与就很难剪枝,所以只考虑直线和直线之间的剪枝,因为存在多条直线交于一点的情况,所以计算出交点之后都要与其他已经计算出来的交点比较,排除重复的点。而可以换过来,每新加入一条直线l的时候,遍历所有已知的交点p,如果交点在l上,则所有过交点p的直线都不会与l交于其他的点,所以可以剔除过交点p的所有直线,当遍历完所有已知的交点,剩下的直线与l的交点一定是新的交点,这样可以一定程度上剪枝。当比较多的直线交于一点的时候,会有比较不错的性能的提升,但平行线多的情况下效果会略有下降,平均来说有一定的优化。

设计实现过程

考虑到附加题的圆和直线是两种不同的数据类型,要保存不同的数据,同时进行相交运算的时候需计算方式不同,但是都要进行相交的运算,所以使用了一个抽象父类Geo定义相交运算的接口,圆和直线各自两个不同的Geo子类,保存各自方程需要的参数。而上文说过仅直线参与运算的时候有剪枝的方法,所以还需要一个点类保存交点和过交点的直线。
在此基础上,上文中提到的点圆的相交中用到一些计算的数据可以预先计算出来,在计算相交的时候直接调用,但是这个在仅有直线参与运算的时候是个负优化,所以直线一开始创建的时候不会直接计算参数,当读完输入之后,如果输入中有圆的存在,再对这些参数预先计算并保存。
这样子有一个接口类Geo,有直线和圆通用的将交点添加到交点集的函数,还定义了求交点的接口,判断点是否在图形上的接口;
Geo子类直线Line类,保存自身所需的参数,实现父类的接口,访问保存的参数的函数,还有为了处理与圆的交点进行数据预处理的函数。
Geo子类圆Circle类,同样保存自身所需的参数,实现父类的接口,访问保存参数的函数。
保存点的坐标和过该点直线的Point类,只有输入中没有圆的时候会被使用。
main类,处理输入,创建给类,调用相应的函数进行处理,将结果输出。
具体各类和各函数的功能参照GitHub中项目的ReadMe,本来main应该仅处理输入输出,将其他的功能交给一个容器类,但是由于时间不足主要是懒没有这么做。
因为有上文中的算法基础,而且求交点的步骤不是很复杂,没有流程分析。
函数构建完之后就是单元测试,主要测试各种情况(直线直线相交,直线圆相交,圆圆相交),在这基础上还有交于一点和交于多点,此外还要测试边界条件,比如要求中提到的(-100000,100000)的取值范围。

程序改进

由于在设计的时候考虑到了附加题的圆的情况,并且设计了仅有直线与直线相交的时候的剪枝的情况,所以结构上没有进一步优化。后续使用vector 保存点和自建的point类、pair类效果不佳,改为struct 结构,使用unorder_set管理点集和点重复的情况,因此删除了点类。
代码分析如下图所示

可以看出,其中最占据运算的函数是计算交点的cross()函数,而cross函数如下图所示

函数中最耗时的一步是unordered_set的插入求出来的交点p的一步,这个难以优化。

关键代码展示

具体来说直线的方程式是\(l:Ax+By+C=0\),而输入为两个不同的点\(p_1(x_1, y_2), p_2(x_2,y_2)\)所以有下列公式得到直线的参数A,B,C:

\[A = y_2 - y_1\\ B = x_1 - x_2\\ C = y_1 * (x_2 - x_1) - x_1 * (y_2 - y_1)\\ \]

直线\(l:A_1 x + B_1 y + C_1 = 0, l2: A_2 x + B_2 y + C_2 = 0\)之间使用\(A_1 * B_2 = B_1 * A_2\)判断是否平行,不平行则一定有交点
交点的计算公式为

\[p(x_p,y_p)\\ x_p = \frac{C_2 * B_1 - C_1 * B_2} {A_1 * B_2 - A_2 * B_1}\\ y_p = \frac{C_2 * A_1 - C_1 * A_2} {B_1 * A_2 - B_2 * A_1};\\ \]

double A2 = ((Line*)g)->getA();
		double B2 = ((Line*)g)->getB();
		double C2 = ((Line*)g)->getC();
		if (A * B2 != B * A2) {
			double temp_x = (C2 * B - C * B2) / (A * B2 - A2 * B);
			double temp_y = (C2 * A - C * A2) / (B * A2 - B2 * A);
			struct Point p { temp_x, temp_y };
			(*set).insert(p);
		}

直线\(l:Ax+By+C=0\)和圆\(C: (x-x_c)^2 + (y-y_c)^2 = r^2\)

使用\(d^2 = \frac{(Ax_c + By_c + C)^2}{A^2 + B^2}=r^2\)判断是否有交点其中\(sq = A^2 + B^2\)

double x = ((Circle*)g)->getX();
double y = ((Circle*)g)->getY();
double d = pow((A * x + B * y + C), 2)/sq;
double s = ((Circle*)g)->getR2() - d;

若有交点, 则垂直于\(l\)的直线方程为\(l2:Bx-Ay+C_2 = 0\),带入圆心\((x_c, y_c)\)求出\(C2 = A*y_c - B*x_c\)

则使用上文的直线与直线相交的方法求出\(l2, l1\)的交点p。

double C2 = A * y - B * x;
double temp_x = (C2 * B - C * A) / (A * A + B * B);
double temp_y = (C2 * A - C * B) / (A * A + B * B);

\(d^2 = r^2\),则\(p\)为切点,仅有一个交点。

否则可以求出过交点的弦的长度的一半\(s = \sqrt{r^2-d^2}\)

直线l的单位向量\(\overrightarrow{e}, p\pm\overrightarrow{e}*s\)为直线与圆的两个交点

				double l = sqrt(s);
				double ex = ((Line*)g)->getEx();
				double ey = ((Line*)g)->getEy();
				struct Point  p1 { temp_x + ex * l, temp_y + ex * l };
				(*set).insert(p1);
				struct Point p2 { temp_x - ex * l, temp_y - ex * l };
				(*set).insert(p1);

\(C1: (x-x_1)^2 + (y-y_1)^2 = r_1^2),C2:(x-x_2)^2 + (y-y_2)^2 = r_2^2\)

使用圆心距\(d^2 = (x_1-x_2)^2+(y_1-y_2)^2\) 判断两圆之间的关系。

		int x2 = ((Circle*)g)->getX();
		int y2 = ((Circle*)g)->getY();
		int R = ((Circle*)g)->getR();
		double a =  (double)x2- x;
		double b = (double)y2- y;
		double r3 = (double)R - r;
		double r4 = (double)R + r;
		double d2 = a * a + b * b;
		double l1 = r3 * r3;
		double l2 = r4 * r4;

\(d^2 = (r_1-r_2)^2\), 两圆内切,不妨设\(r_1<r_2\)

则切点\(p = c_1+\overrightarrow{c_2,c_1}*\frac{r_1}{d}\)

			double d = sqrt(d2);
			if (r < R) {
				struct Point p { x + ((double)x - x2) * r / d, y + ((double)y - y2) * r / d };
				(*set).insert(p);
			}
			else {
				struct Point p { x2 + ((double)x2 - x) * R / d, y2 + ((double)y2 - y) * R / d};
				(*set).insert(p);
			}

\(d^2 = (r_1+r_2)^2\), 两圆外切

则切点\(p = c_1+\overrightarrow{c_1,c_2}*\frac{r_1}{d}\)

                        double d = sqrt(d2);
			struct Point p { x + ((double)x2 - x) * r / d, y + ((double)y2 - y) * r / d };
			(*set).insert(p);

若两圆相交则参照上述这篇文章中的第二种方法,

if (d2 > l1&& d2 < l2) {
			double d = sqrt(d2);
                       double s1 = (r2 - ((Circle*)g)->getR2() + d2) / 2 * d;// s1对应文章中的a
			double h = sqrt(r2 - s1*s1);
			double x0 = x + s1 / d * ((double)x2 - x);
			double y0 = y + s1 / d * ((double)y2 - y);
			double cx = h / d * ((double)y2 - y);
			double cy = h / d * ((double)x2 - x);
			struct Point p1 { x0 - cx, y0 - cy };
			(*set).insert(p1);
			struct Point p2 { x0 + cx, y0 + cy };
			(*set).insert(p2);
		}

Code Quality Analysis截图

posted @ 2020-03-09 21:13  hunry6  阅读(187)  评论(1编辑  收藏  举报