• 个人项目作业(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}, ppmoverrightarrow{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截图

  • 相关阅读:
    专题三--1005
    专题三--1009
    专题三--1017
    背包九讲
    专题三--1003
    专题三--1004
    专题三--1015
    [洛谷P1220]关路灯
    [洛谷P1776]宝物筛选
    [USACO14JAN]Recording the Moolympics
  • 原文地址:https://www.cnblogs.com/hunry6th/p/12451593.html
Copyright © 2020-2023  润新知