BUAA软工__我的项目做业

项目 内容
这个做业属于哪一个课程 2020春季计算机学院软件工程(罗杰 任健) (北京航空航天大学 - 计算机学院)
这个做业的要求在哪里 我的项目做业
个人教学班级 005
这个项目的GitHub地址 https://github.com/LastWhisper1/IntersectionCounter

PSP项目表格

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

从实际结果来看,整个项目的实现时间仍是太长了。我码代码的能力果真仍是使人捉急,虽然前期有过很复杂的想法,但在具体编码时并无获得实现,对最后的程序性能也不太满意。虽然不熟悉C++能够做为一部分缘由,但总的来讲在效率上仍是有很大的提高空间,从此仍是要增强本身的编码能力,避免有想法而实现不了的状况。c++

解题思路描述

为了不各类偏差对结果形成的影响,解题中避免了使用浮点数,具体的实现方式上,使用了如下三个类:git

1. 直线类Line

直线采用最万能的标准式 $$ Ax+By+C=0 $$ 来保存,经过给定的两点计算出三个参数便可,而且无需化简。github

这个类中还实现了检验两条直线是否平行的函数,因为不考虑重合,只需对两条直线 $$ \left{\begin{matrix} & A_{1}x + B_{1}y + C_{1}=0\ & A_{2}x + B_{2}y + C_{2}=0 \end{matrix}\right. $$ 验证是否成立 $$ A_{1}B_{2}=A_{2}B_{1} $$ 便可。框架

2. 交点类Intersection

题目要求已经确保直线不会重合,经过函数验证两条直线不平行后,方程 $$ \left{\begin{matrix} & A_{1}x + B_{1}y + C_{1}=0\ & A_{2}x + B_{2}y + C_{2}=0 \end{matrix}\right. $$ 的惟一解是 $$ \left{\begin{matrix} x = \frac{C{{1}}B{{2}}-C_{2}B_{1}}{B{{1}}A{{2}}-B_{2}A_{1}}\ y = \frac{A{{1}}C{{2}}-A_{2}C_{1}}{B{{1}}A{{2}}-B_{2}A_{1}} \end{matrix}\right. $$函数

在保存时,分别保存$x, y$的分子和分母(都是整数),这样就避免了浮点数致使的精度问题。若是接下去考虑把圆加进来的话,能够再额外保存根式下的分子和分母。但程序中没有实现圆相关的部分,故再也不赘述。性能

3. 交点数统计类Counter

每读入一条直线便与每一条现有直线比较,若两条直线不平行,则计算交点值并放入set容器中,最后将新读入的直线放入vector容器中。这样作的话,若是使用unordered_set,在平均状况下的时间复杂度为$O(n^{2})$,若使用set则为$O(n^{2}logn)$。单元测试

在set与unordered_set的选择中,发现程序在输入规模 $N = 10000$ 时,使用set会致使超过60秒时间限制,但使用unorder_set会致使bad_alloc异常。通过权衡,既然想不到更好的方法提高性能,不如使用set尽量保证准确性,也能避免使用unorder_set后因hash函数致使的bug。学习

4. 优化上的一些没实现的想法

程序中,存储和计算交点是必不可少的工做量。对每一个交点,若是在计算中,发现直线经过某个交点,那么该直线和经过这个交点的全部直线,都不可能再有其余交点,这样就能够省去和一些直线计算交点的步骤。考虑到性能测试中交点个数$h$远小于$N(N-1)/2$,暗示多线共点的状况较多,那么这种方法能起到比较大的优化做用。测试

针对平行线的优化可能收效不大,举个例子,若一条直线与一组$m$条平行线中的某一条相交,能够知道该直线与该组平行线共产生了$m$个交点,但因为存在多线共点的状况,仍须要计算每一个交点的位置。优化

设计实现过程

如上一段所说,我实现了Line,Intersection,Counter三个类,并采用了上面所说的容器。在命令行参数的识别上,采用比较普通的while循环来实现参数的读取和处理。下面主要讲一下使用的测试方法与测试样例的构建。

在构建的单元测试样例中,我作了基本的功能测试和边界测试。在基本的功能测试中,我尽量测试了每一个函数的功能是否正确,包括直线标准式是否正确、交点计算是否正确(也就是Line和Intersection的构造函数),同时构建了几个比较基础的样例测试。在边界测试中,考虑了诸如边界点肯定直线、两条直线的交点很是接近的状况进行样例构建,测试结果并无问题。下面这张图描述了其中一个测试样例的构建:

在性能方面的压力测试中,我另写了一个cpp文件(代码不在git仓库中),使用随机生成的方法构建大量样例。虽然这样生成的直线并不能保证不重合(实际上几率很是小),但在我本身的程序逻辑中,两条直线重合被视为平行,不会引起崩溃性的bug,故在性能测试中能够接受。实际上,随机生成的直线几乎不存在平行或重合的状况,得出的交点数基本都是$N(N-1)/2$。

程序性能改进

程序写好之后,实际上并无作大的框架上的改动,但在细节处理上仍是改过不少地方。例如,判断两条直线是否平行的函数和Counter类中插入新直线的函数,因为函数体较为简短,故改为了inline函数提升性能。另外,程序中还尝试使用double代替以前介绍的分数表示方法来存储交点,实际显示性能提高不是那么明显。以下是在使用set容器存储交点集合,$N = 5000$时性能测试的截图:

从截图中能够看出,程序运行时间中至关一部分都在set的RB树构建上(固然以前测试的时候也对其余时间占比较大的地方进行了优化)。

若把set替换成unordered_set的话,性能会有比较明显的提高(经测试,在相同$N$值下,平都可减小$1/3$执行时间),但因为没办法很好解决掉内存分配异常的问题,加上hash函数可能存在错误,故仍是采用set容器尽量保证程序正确性。

关键代码说明

首先展现交点的构造函数,这里的A1, B2等使用了宏定义,对应了两条直线标准式的相应参数,具体含义以前的部分已经说起:

Intersection::Intersection(Line* line1, Line* line2) {
	int xnume = C1 * B2 - C2 * B1;
	int xdeno = B1 * A2 - B2 * A1;
	int ynume = A1 * C2 - A2 * C1;
	int ydeno = xdeno;
}

代码中的xnume对应交点x坐标的分子,xdeno对应x的分母,y坐标依此类推。经过分子分母分别存储的方法,避免了浮点数可能带来的精度问题。

下面是整个程序的核心函数CountIntersections:

int Counter::CountIntersections() {
	for (size_t i = 0; i < lineSet->size(); i++) {
		Line* line1 = lineSet->at(i);
		for (size_t j = 0; j < i; j++) {
			Line* line2 = lineSet->at(j);
			if (!line1->isParallel(line2)) {
				Intersection intsec(line1, line2);
				intersectionSet->insert(intsec);
			}
		}
	}
	return intersectionSet->size();
}

代码的逻辑在上文中已经介绍过了,这段函数中的isParallel()为平行线的断定函数,并写成了内联函数以提升性能。若想要继续提升这个函数的性能,除了将set改为unordered_set以外,只能从循环条件出发,略去一些没必要要的交点计算。

Code Quality Analysis截图

还请老师助教提出批评意见。

3/10追记:在和结对搭档对拍程序的时候,发现了比较严重的bug,初步判断是Intersection类中运算符重载错误引发的,但短期内难以找出消除bug的方法。在以前和搭档对double精度的讨论中,同伴的观点是double精度足够完成实验需求,因此在最后一次提交时,利用程序中预留的宏定义开关将Intersection类改成double实现,直接存储交点x, y坐标的double值(在博客代码的基础上作除法便可获得)。

相关文章
相关标签/搜索