交点计数 -- 软工个人项目作业
PSP 2.1
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 30 | 60 |
· Design Spec | · 生成设计文档 | 30 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 0 | 0 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
· Design | · 具体设计 | 30 | 50 |
· Coding | · 具体编码 | 60 | 90 |
· Code Review | · 代码复审 | 60 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 120 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 20 | 20 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 40 |
合计 | 400 | 500 |
解题思路
基本问题
直线公式
对于复杂问题而言,我们首先寻求将问题简化。我们将问题简化到求两条直线的交点。
由解析几何的知识我们知道,通过两条直线的公式可以求出他们的交点。为了简化问题,我使用直线的一般式来表示直线: Ax+By+C = 0.
A,B,C 可以由题目中给定的两点坐标计算得出(设两点为 (x_1,y_1),(x_2,y_2) ):
- A=y_2−y_1
- B=x_1−x_2
- C=x_2y_1−x_1 y_2
交点的计算方法
通过联立上述直线公式,我们可以得到两条直线的交点。
交点x = {B_1C_2-B_2C_1} / {A_1 * B_2-B_1 * A_2}
交点y = {A_2 * C_1-C_2 * A_1} / {A_1 * B_2-B_1 * A_2}
存储方法
我们选用Vector
存储过程中产生的交点。在所有交点计算完毕后,我们对数组内元素排序,并去重。
复杂度
由于在获得直线交点时,需要枚举两条直线的所以可能。所以时间复杂度为O(n^2)。
设计和实现过程
本设计中包含两个类(直线、圆)和一个结构体(坐标)。结构体作为坐标的数据结构,具有比较等功能。而两个类中都有和其他对象判断交点的方法。主函数通过对每个直线、每个圆建立相应对象,再枚举调用交点计算方法即可找出所有的交点。
交点
stuct Position {
double x;
double y;
};
由于我们在后续需要对交点进行排序和去重处理,所以我构造和posCompare
,posEqual
两个函数来比较两个交点的大小和是否相等。
直线:
在直线类中我们存储直线一般式的三个参数\(A,B,C\)。
直线可以相交,我们为直线构造相交方法,返回交点结构体。
Class Line {
long long a, b, c;
Line(long long x1, long long y1, long long x2, long long y2);
struct Position intersect(Line&);
}
圆
在圆的类中,我们存储能够定位一个圆的三个参数:\(x,y,r\),即圆心和半径。
同时圆的类中,包含圆与直线相交、圆与圆相交的方法。
class Circle
{
public:
Circle(long long x, long long y, long long r);
long long x, y, r;
void lineIntersect(const Line&, vector<struct Position>&);
void circleIntersect(const Circle&, vector<struct Position>&);
};
在计算直线和圆的交点时,我们采用了点到直线的距离公式。distance = |Ax0+By0+C| / sqrt(A2+B2),如果点到直线的距离大于半径,则不会有交点,如果小于或等于半径,则会有0或1个交点。而圆和圆之间的交点,我们采用了向量公式法来进行计算。
性能分析
在本节中我们对代码进行了性能分析。通过随机生成的2000条直线进行测试。从图中可以看出,我们代码的主要耗时部分在于去重时对代码进行的排序操作,占到了所有消耗的80%左右。对于重复结点的删除,我们有两种可以选择的方法,一种是进入时检查,即使用set等维护一个互不重复的结点结合,另一种是输出时检查,此时我们需要额外进行去重处理。二者都需要消耗额外的时间,目前还没有一个很好地降低时间复杂度的方法。
测试
单元测试设计
单元测试主要针对结点的比较函数和交点的计算方法进行。主要的要求是要考虑所有的情况,例如,要考虑结点比较时可能出现的大于、小于、等于等情况,直线交点计算时的相交、平行、垂直等情况,圆和圆之间的相切、相交、相离等其情况。
消除 Code Quality Analysis 中的所有警告
关键代码说明
交点计算方法
这里直接调用公式即可。注意到笔者在这里整数类型都选取的是long long型,虽然我们的坐标值等变量都不会超过int的范围,但是在整数进行乘法运算时,可能有溢出的风险,所以使用long long能够更保证程序的正确性。
结构体(位置)比较函数
我们使用结构体struct Position
来存储交点的坐标。由于在最后我们需要对这些坐标进行去重操作,所以我们需要设置坐标的比较函数,判断二者的大小和相等性。