软件工程基础 第3次个人作业

一点说明

这篇博客是软件工程基础(罗杰、任建)的第三次课程作业(个人项目作业)

项目 内容
这个作业属于哪个课程 软件工程基础(罗杰,任建)
这个作业的要求在哪里 作业要求的链接
我在这个课程的目标是 提升对软件工程的宏观和微观的全面认识,并加以实践
作业在哪些方面帮我实现目标 亲身实践个人项目开发的完整流程
我的教学班级 006
我的GitHub项目地址 https://github.com/SnowOnVolcano/IntersectProject.git

PSP表格

在开始实现程序之前,在下述 PSP 表格记录下你估计将在程序的各个模块的开发上耗费的时间。(0.5')

在你实现完程序之后,在下述 PSP 表格记录下你在程序的各个模块上实际花费的时间。(0.5')

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

基本需求

1. 解题思路

解题思路描述。即刚开始拿到题目后,如何思考,如何找资料的过程。(3')

  • 思路一

    读完问题的第一思路,是利用高中学到的 一般方程法 进行交点的求解:
    \[ 设直线方程为\ ax+by+c=0,\ 已知直线过两点(x_1,y_1)和(x_2,y_2),则有\\ \begin{cases}ax_1+by_1+c=0\\ax_2+by_2+c=0\end{cases}\implies \begin{cases}a=y_1-y_2\\b=x_2-x_1\\c=x_1y_2-x_2y_1\end{cases}\\ 设两直线分别为\ a_1x+b_1y+c_1=0\ 和\ a_2x+b_2y+c_2=0,联立两直线方程,得\\ \begin{cases}a_1x+b_1y+c_1=0\\a_2x+b_2y+c_2=0\end{cases}\implies \begin{cases}x=\frac{b_1c_2-b_2c_1}{a_1b_2-a_2b_1}\\y=\frac{a_2c_1-a_1c_2}{a_1b_2-a_2b_1}\end{cases}\implies \begin{cases}x=\frac{b_1c_2-b_2c_1}{D}\\y=\frac{a_2c_1-a_1c_2}{D}\end{cases},其中\ D=a_1b_2-a_2b_1 \]
    这样的解法有几个好处:

    • 可以较好地处理特殊情况,比如,可以通过判断 D 是否为 0,直接判断两直线是否平行;
    • 这种方法直接计算出交点的坐标,这样可以设置一个 set 用来存放已得到的交点,直接在存入时进行除重,最终结果输出 set 的大小即可;

    当然,其缺点也很明显:

    • 计算简单粗暴,对于具有 N 条直线的样本,需要进行 \(C_N^2\) 次计算,时间复杂度为 \(O(n^2)\)
    • 如果简单地使用 set<(x,y)> 进行的方式进行存储,则需要进行浮点运算,这既会带来精度的损失,也会加长运行时间。
  • 思路二

    如果不采用直接计算的方法,如何得出交点个数呢?我想到的另一个方法是,做减法

    如果任意的两条直线都相交且任意的三条直线不交于同一点,那么对于具有 N 条直线的样本,交点的个数为 \(C_N^2\),对一般情况就有,
    \[ 总交点数=C_N^2-平行直线对的数目-\sum_{所有的交点}{(同一交点的直线数目-2)} \]
    但是,仔细想想,这种方法似乎并不比思路简单,因为我没有想到好的算法去计算同一交点的直线数目……

  • 思路三

    想不到好的算法,我最后只能决定使用直接计算的方法,但是我想其实思路一的方法还可以简单地优化一下细节,减小精度损失,有两个方法,

    • 在存放交点时,交点的纵横坐标的分子分母分开存储,这样可以规避浮点运算,从而保证精度;
    • set 的排序函数进行重载,设置一定的精度范围,这样可以一定程度地减小精度损失,但是无法从根本上避免。

    我最终选择了后者,以平衡精度和时间复杂度。

2. 实现过程

设计实现过程。设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?单元测试是怎么设计的?(4')

  • 结构设计(2个结构体)
    • Point:表示点 \((x,y)\),其中 Point.xPoint.y 分别表示点的纵横坐标;
    • Line:表示直线 \(ax+by+c=0\),其中 Line.aLine.bLine.c 分别对应直线的三个参数;
  • 函数实现(2+1+1个函数)
    • PointLine 的构造函数,计算两直线交点的函数 calLineLineIst(...) ,主函数;

    • 各函数关系如下,

      image-20200310135902543

  • 单元测试

    我主要进行了三个方面的单元测试:

    • 构造函数是否能正确初始化:包括参数的传递和计算;
    • 交点计算函数是否能覆盖所有情况:包括多线一点、平行的情况,以及直线与坐标轴平行等情况;
    • 交点集合的精确度是否能保证:重点测试了以下两种情况,1)在交点相差较小的情况下,是否能够区分开不同的交点;2)在交点的小数点位数较多时,是否能保证交点不重复。

    以下是我的一些单元测试的测试点的图形示意:

3. 性能改进

记录在改进程序性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由 VS 2019 的性能分析工具自动生成),并展示你程序中消耗最大的函数。(3')

  • 在改进之前,我进行了一次程序性能的测试,我发现,最费时的操作是 set 的插入时,遍历红黑树的过程。反而计算交点的函数没有花费太多的时间。
  • 但是,在尝试了非红黑树的集合之后,我发现有时候正确性得不到保证,所以我最后还是选择了保持原有的 set。
  • 其他的优化都是小修小补了,比如,
    • 优化了代码结构,将点和直线的初始化函数放入了结构体内;
    • 除了最后的计算,中间过程使用整型,而非浮点型
  • 优化后的图如下
img
  • 程序中消耗最大的函数如下
img

4. 代码说明

代码说明。展示出项目关键代码,并解释思路与注释说明。(3')

  • 计算直线与直线交点(思路请见代码注释)
    // calculate the intersections of two lines
    static void calLineLineIst(Line& line1, Line& line2) {
      int D;
        D = line1.a * line2.b - line2.a * line1.b;
      switch (D)
      {
      case 0: // parallel
          break;
      default:
          //  line1: a1*x+b1*x+c1=0, line2: a2*x+b2*x+c2=0
          //  ==> x=(b1*c2-b2*c1)/(a1*b2-a2*b1),
          //      y=(a2*c1-a1*c2)/(a1*b2-a2*b1)
          //  let D=a1*b2-a2*b1
          //  ==> x=(b1*c2-b2*c1)/D, y=(a2*c1-a1*c2)/D
          Point point = { 
              (line1.b * line2.c - line2.b * line1.c) / (float)D, 
              (line2.a * line1.c - line1.a * line2.c) / (float)D 
          };
          points.insert(point);
          break;
      }
    }
  • 集合的排序和精度的确定

    bool operator == (const Point& other) const {
      return fabs(x - other.x) < 0.00000001 && fabs(y - other.y) < 0.00000001;
      }
    bool operator < (const Point& other) const {
      if (x != other.x) {
          return x < other.x;
      }
      else {
          return y < other.y;
      }
    }

附加题

1. 解题思路

\[ 已知两圆\ (x-x_1)^2+(y-y_1)^2=(r_1)^2\ 和\ (x-x_2)^2+(y-y_2)^2=(r_2)^2\ ,\\设圆心距为\ d,则有\ \ \ \begin{cases}两圆内含或相离, &d<|r_1-r_2|\ 或\ d>r_1+r_2\\两圆相交或相切,&|r_1-r_2|\leq d\leq r_1+r_2\end{cases}\\当两圆相交或相切时,两圆的方程相减得到过两圆交点(切点)的直线,联立直线与任一圆即可算出交点,\\\begin{cases}(x-x_1)^2+(y-y_1)^2=(r_1)^2\\(x-x_2)^2+(y-y_2)^2=(r_2)^2\end{cases}\implies 2(x_2-x_1)x+2(y_2-y_1)y+x_1^2-x_2^2+y_1^2-y_2^2+r_2^2-r_1^2\\\implies \begin{cases}(x-x_1)^2+(y-y_1)^2=(r_1)^2\\ 2(x_2-x_1)x+2(y_2-y_1)y+x_1^2-x_2^2+y_1^2-y_2^2+r_2^2-r_1^2\end{cases}\implies 求交点坐标 \]

2. 代码说明

  • 计算直线与圆交点(思路请见代码注释)
    // calculate the intersections of line and Circle
    static void calLineCircleIst(Line& line, Circle& circle) {
      int intercept;
      // intercept=r^2-d^2=r^2-(ax+by+c)^2/(a^2+b^2)
      intercept = (int)(pow(circle.r, 2) - pow(line.a * circle.x + line.b * circle.y + line.c, 2) / (pow(line.a, 2) + pow(line.b, 2)));
      // not intersect
      if (intercept < 0) { return; }
      // tLine is perpendicular to line
      Line tLine = { line.b, -line.a, line.a * circle.y - line.b * circle.x };
      int D;
      D = tLine.a * line.b - line.a * tLine.b;
      // tPoint is the intersection of line and tLine
      Point tPoint = {
          (tLine.b * line.c - line.b * tLine.c) / (float)D,
          (line.a * tLine.c - tLine.a* + line.c) / (float)D
      };
      switch (intercept) 
      {
      case 0: // line is tangent to circle
          points.insert(tPoint);
          break;
      default:// line passes through circle
          float vecX;
          float vecY;
          float offset;
          // (vecX, vecY) is a unit vector
          vecX = (float)(line.b / sqrt(pow(line.a, 2) + pow(line.b, 2)));
          vecY = (float)(-line.a / sqrt(pow(line.a, 2) + pow(line.b, 2)));    
          // Offset is half of the intercept
          offset = (float)sqrt(intercept / (pow(line.a, 2) + pow(line.b, 2)));
          // intersection = tPoint +/- vec*offset
          Point ist1 = { tPoint.x + vecX * offset, tPoint.y + vecY * offset };
          Point ist2 = { tPoint.x - vecX * offset, tPoint.y - vecY * offset };
          points.insert(ist1);
          points.insert(ist2);
          break;
      }
    }
  • 计算圆与圆交点(思路请见代码注释)
    // calculate intersections of two circles
    static void calCircleCircleIst(Circle& circle1, Circle& circle2) {
      int radiusSum;
      int radiusDiff;
      int centerDis;
      radiusSum = (int)pow(circle1.r + circle2.r, 2);
      radiusDiff = (int)pow(circle1.r - circle2.r, 2);
      centerDis = (int)(pow(circle1.x - circle2.x, 2) + pow(circle1.y - circle2.y, 2));
      // not intersect
      if (centerDis > radiusSum || centerDis < radiusDiff) {
          return;
      }
      // line passes both two intersections of circles
      Line line = {
          circle1.d - circle2.d, 
          circle1.e - circle2.e,
          circle1.f - circle2.f
      };
      // the intersections of two circles are also the intersections of line and circle
      calLineCircleIst(line, circle1);
    }

截图 - 测试与度量

1. 单元测试覆盖率

img

2. Code Quality Analysis 警告消除

img

猜你喜欢

转载自www.cnblogs.com/FUJI-Mount/p/12456311.html