课程:北航-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安全形式。