BUAA OO 第三单元总结

BUAA OO 第三单元总结

写在前面

本单元涉及到的是JML规格的相关内容,即课程组给出了代码的JML规格,让我们自己来选择实现,以完成相对应的功能。相对于前两个单元来说,本次作业的难度降低,加大了对于代码理解和编写过程中细节的考察。

实现规格所采取的设计策略

一句话概括一下就是:先理解再动手,先实现再优化。

首先肯定是要仔细阅读所提供的JML规格,充分理解规格的作用和其需要完成的任务,在动手之前也先想好要用到什么内容(选用什么容器啦,要用什么数据啦,需不需要修改原本的数据等等),这一点无可厚非。

第二步就是先试着动动手,暂不考虑性能以及该规格与原有内容之间的内在联系,只是按照规格开始一步一步地实现,这一步只要能正确实现规格要求的内容即可(TLE警告)

第三步主要就是性能问题了。笔者推荐在此之前先进行第二步是因为,第二步实现的代码虽然性能可能不太理想,但有关规格的理解相比于空想可谓提升显著。笔者的优化主要涉及到了以下内容:用到的数据是否可以和原有数据或结构产生联系,如果有的话是否可以通过对原有数据进行修改或替换,这样可以避免新建数据产生的复杂问题;用到容器的规格是否可以进一步优化,以缩短时间复杂度,或是否可与原有数据产生联系,以加强新内容与原来内容的联系,比如把Person类中的acquaintancevalue合并;第二步中实现的算法是否可以优化,比如在取最短带权路径是把遍历换成dijkstra算法。

测试方法和策略

笔者的测试方法和策略是:重难点函数手动验证边界数据点,其余函数和性能问题用自动生成数据和对拍机实现。

举个栗子:在第一次作业中,qbs函数是一个重点内容,通过理解我们可以知道该函数是用来统计交互系统中群体(相互联系的人组成的集体)的个数。所以该函数的正确性通过手动输入极限数据,比如0等一些边界数值,在确保正确之后,通过自动数据生成器生成大数据来看程序运行的时间。

容器选择和使用经验

  • 容器选择

由于各个对象的id具有唯一性,所以采用的是HashMap来充当绝大多数的容器,其中建立了从id到具体对象的映射关系。这样的好处是,绝大部分的内容由库函数提供,人为添加的代码较小,且相比于ArrayList等容器,其所用的时间大大减少。

但仍有少部分的容器选择是LinkedList,比如Person类中的messages,原因是每次添加新message时要求加在第一位,而HashMap是无序的,无法实现类似的功能。

还有用到的就是HashSet,由于和每个人有联系的人可能有很多,所以在储存关系的时候使用了类型为HashMap<Integer, HashSet<Integer>>的容器。

  • 使用经验

使用经验的话,笔者将容器分成了两类:HashMap和其他。如果要储存的数据有独一无二的属性,且对储存顺序没有要求,则首选HashMap;如果不满足上述条件,或者是有其他要求,再按照要求选择,比如要求对储存数据进行排序的话,笔者可能就选择TreeMap了。

架构设计和性能分析

由于笔者的代码在架构中穿插有性能方面的优化,所以就放在一起说啦~

异常类:

三次作业中的异常类都很类似,所以将所有的异常类单独抽出来说。

异常类内部设置一个全局变量num用来统计调用该异常类的次数,再设置一个HashMap<Integer, Integer>类型的变量,用来记录调用的Personid和该id调用次数的映射。然后再加上print函数用来输出对应的信息,这样就完成了异常类的设计。

第一次作业:

第一次的作业与后两次相比较为基础,只是简单地实现了一下基础的操作,大部分都可以直接照着规格写,唯一涉及到了架构设计的地方就是qbs函数和isCircle函数了。

如果按照规格提供的做法来实现qbs函数的话,由于其中通过遍历不断地调用isCircle函数,所以会导致超时。于是,笔者的做法是,新建一个HashMap<Integer, HashSet<Integer>>的容器,用来表示和某一id相关联的用户的id,这样可以避免isCircle函数中的遍历问题,从而大大减少代码的时间复杂度。具体操作为:因为每一次添加用户间的关系的时候都是通过addRelation函数,因此在该函数中,加入一点修改,每次添加时进行判断,如果Person2本身就在Person1的关系列表中,则不做任何操作;如果不在的话,则合并二者的关系列表,遍历其中的所有用户,并将他们的关系列表也改成合并后的关系列表,即可实现对关系的实时添加。

第二次作业:

第二次作业虽然实现上增加了更多的函数,但对性能的要求没有那么强烈。笔者只是简单优化了新的有关年龄的函数和对上次的qbs函数更进一步地改进。

首先是求年龄平均值和方差的函数的规格给出的是遍历的方法,这样的时间复杂度很高,而我们也可以观察到,每一个用户添加到Group的时候,都是通过addPerson函数,同样删除也是仅调用delPerson函数,所以笔者在Group类中新建了两个整型变量来储存组内的年龄和和年龄平方和,以此来化简求平均值和方差的函数。值得一说的是,笔者选择了储存平方和然后用(sumOfAge2 - 2 * sumOfAge * ageMean + size * ageMean * ageMean) / size这样的公式来求方差的值,之所以没有用和的平方和平方和进行运算,是因为后者可能在整数除法中删掉除不尽的部分,从而导致精度损失。

再者就是第一次中的qbs函数,因为笔者在做第二次作业的过程中对整体构造有了进一步的理解,加上向大佬的询问,笔者发现每一次新建Person的时候,对应的块数应该要加一,而每次合并两个关系列表的时候对应的块数要减一。于是乎,发现了新大陆的笔者在NetWork中新建了变量来表示当前的块数,然后在新建用户和合并用户间的关系列表的时候对其进行操作,之后在qbs函数中直接将其输出,使得时间有了较大程度的提升。

第三次作业:

第三次作业在难度上是三次中最大的,需要补充的代码较多,且大多数是在原有基础上做的维护,如果对代码整体框架不熟悉的话可能会产生较大的问题。

但第三次作业在优化上的空间不大,只是需要注意最后的sendIndirectMessage函数中涉及到图论的带权最短路径问题。笔者采用的是小优化后的dijkstra算法:首先建立两个HashMap,一个HashSet和一个startPerson对象,两个HashMap中一个用来存已经找到最短路径的点的id和最短路径的数,另一个存还没有找到的点和到其需要的路径数(记录到未找到最短路径的点的权值),HashSet存未找到最短路径的点的idstartPerson则表示当前新找到的点。完整算法如下:首先遍历startPerson和未找到最短路径的点之间的权值,用新的权值更新负责记录的HashMap,然后从其中找到最短的点,将startPerson更新为这一点,然后把它从表示未找到的集合移动到表示找到的集合中,一直重复上述过程,直到找到所求的点。这样做的话,相比于遍历的算法时间复杂度有了巨大的提升。

图模型构建和维护策略:

本单元作业是个有关图论的问题,笔者图模型构建的方法是:首先大概理解了各个类之后,在纸上先写上各个类之间的名字,然后将他们之间用函数连起来,这样先产生一个大体上的框架;然后,把没有用来交互的函数(比如什么qbs之类的)写到对应的类下面;注意,笔者是最后才在纸上添加所要用到的数据,原因是笔者认为数据之间的联系大于数据本身,但顺序本身就见仁见智,读者大大们选择自己喜欢的顺序就可以啦。完成后这样就可以很清楚地知道了各个类在交互系统中起到的作用,也可以清晰地看出每个数据和函数之间的内在逻辑联系,对代码的完成和优化起到了很大的帮助。

而维护策略是在建立了图模型的基础上实现的:将每次新加的类添到上一次作业的图中,并把新函数也写上去,同样也是最后再添加需要用到的数据。

在这里读者大大可能会产生疑问:是应新建数据还是修改原有数据?笔者认为一味地新建只会让代码变得冗余,许多数据只有一处使用,白白占用空间;而如果全部用原有的数据,会使得每一个数据都变得复杂,以后修改时很容易”牵一发而动全身“,也加大了对代码的维护难度。所以说,关于这一点,笔者认为如果关联性特别大,就修改原有的数据,否则的话还是新建变量吧。

这样修改完图模型后,就可以一眼看见新的工作是什么了,从而进行很好地维护。

posted @ 2021-05-29 14:13  Zhu_zk  阅读(72)  评论(0)    收藏  举报