项目 | 内容 |
---|---|
作业属于哪个课程 | 2020春季计算机学院软件工程 |
作业的要求 | 个人项目 |
课程的目标 | 阅读教材前三章,完成作业 |
软件工程个人作业
解题思路
这一道题和我曾经做过的一道题很像,那道题是在保证所有直线无公共交点情况下求总交点个数。此题可以用动态规划求解。
所以我的第一思路是考虑能否使用动态规划等思想来降低复杂度,然而我发现因为在本题中存在直线间有公共交点的可能性,我们不可避免地需要计算出所有的交点坐标以便比较,所以我们的时间复杂度至少为O(n^2)。
在确定了要计算所有交点的情况下,再考虑计算交点与比较交点的耗时。前者复杂度为O(1),后者则与所选择的数据结构有关。
查找我想到了三种思路:
- 将交点存储在数组中,然后进行遍历查找,此操作复杂度O(n2)(最多有n2/2个交点)。
- 将交点用平衡的二叉树进行存储,如红黑树,此时插入、查找的复杂度均为O(logn)。
- 将交点用hash表进行存储,此时复杂度为O(1)。
在这三者中我对于2,3进行了尝试。
最后,考虑到这次作业是为后续作业做准备,我选择了学习c++的面向对象的知识,希望用oo来写这次作业。
我的设计
以必做题进行说明。
我构造了结构体node来表示点,以及类Line来表示直线。
在读取到每一根直线后,我们将直线存进line_set中,我们将其与之前所有的直线求交点,并把新得到的交点与node_set中的所有点进行比较,如果无重复则将该点加入到node_set中。
此时,我们可以发现,对于line_set,我们每次都会遍历它,所以用数组即可。
对于node_set,需要进行查找。在查阅c++标准库过后,我发现有这两个STL容器可以很好的满足我们的需求:set和unordered_set,前者的底层是由红黑树实现的,后者是由hash表实现的,我对于两者分别进行了尝试。
设计的缺陷
整体设计上,缺乏对于非法命令行与非法输入数据的处理。
代码说明
直线类:
class Line
{
public:
Line();
Line(double x1, double y1, double x2, double y2);
~Line();
bool getExistK() {
return exist_k;
}
double getX1() {
return x1;
}
double getY1() {
return y1;
}
double getX2() {
return x2;
}
double getY2() {
return y2;
}
double getK() {
return k;
}
double getA() {
return A;
}
double getB() {
return B;
}
double getC() {
return C;
}
private:
double x1, y1, x2, y2, k;
double A, B, C;
bool exist_k;
};
};
Line::Line() {
this->x1 = 0;
this->x2 = 0;
this->y1 = 0;
this->y2 = 0;
this->A = 0;
this->B = 0;
this->C = 0;
this->k = 0;
this->exist_k = false;
}
Line::Line(double x1, double y1, double x2, double y2)
{
this->x1 = x1;
this->x2 = x2;
this->y1 = y1;
this->y2 = y2;
this->A = y1 - y2;
this->B = x2 - x1;
this->C = x1 * y2 - x2 * y1;
this->k = 0;
if (x1 == x2) {
this->exist_k = false;
}
else {
this->exist_k = true;
this->k = (y2 - y1) / (x2 - x1);
}
}
Line::~Line()
{
}
点类:
struct node findIntersection(Line a, Line b)
{
struct node ans;
double x = 0, y = 0, d;
double a1 = a.getA(), b1 = a.getB(), c1 = a.getC();
double a2 = b.getA(), b2 = b.getB(), c2 = b.getC();
d = a1 * b2 - a2 * b1;
if (d != 0) {
x = (b1 * c2 - b2 * c1) / d;
y = (a2 * c1 - a1 * c2) / d;
}
ans.x = x;
ans.y = y;
return ans;
}
在尝试使用set时,因为是红黑树,需要进行排序,所以要重载<符号。
函数findIntersection输入两个直线类,返回它们的交点,这里要注意输入的直线已经经过判断,保证了交点的存在。
函数void add_node输入两条直线,判断是否平行,如果平行则直接返回,否则调用findIntersection,再判断交点是否重复,不重复则将其加入node_set。
我的改进与问题
在上文中提到我分别尝试了unordered_set与set。
在前者,我编译时出现了如下bug:
经查阅,我发现这是因为ununordered_set在插入、删除操作时会移动内部的对象,这时可能会使用对象的构造函数与析构函数,但对于我自己写的类,这些函数可能不能直接共unordered_set使用。遗憾的是,我能查阅的资料都没有告诉我应该怎样通过修改构造与析构函数来解决问题,而是建议将key改为简单类型,遂放弃。
之后我尝试使用了set,完成了作业。
使用code quality analysis后,消除了所有warning。
使用性能分析工具后,效果如下:
在调用外部代码上占用资源最多,我对于外部代码调用主要是对于set的访问,这里我进行了微小的修改,主要是在访问node_set后将要比较的点提取出来,以免在某次比较是多次访问set,但并没有改变性能。
最后是单元测试,在这里,我首次写完单元测试代码后,发现并不能进行生成,报错是我使用的类进行了重定义,查阅资料后发现需要将类定义等写入单独的.h文件。在修改后即可通过单元测试。
单元测试主要是测试的交点坐标求解。相关插件没能成功安装,对于测试粒度等没能得到。
PSP时间分析
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 15 | 15 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 60 | 75 |
· Design Spec | · 生成设计文档 | 10 | 15 |
· Design Review | · 设计复审 (和同事审核设计文档) | 5 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 15 |
· Design | · 具体设计 | 20 | 30 |
· Coding | · 具体编码 | 60 | 90 |
· Code Review | · 代码复审 | 30 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改 | 20 | 30 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 20 | 20 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 20 |
合计 | 280 | 390 |