oo_第三单元博客
引言
本单元主要的学习内容为JML规格语言,通过阅读JML规格来实现一个社交网络及其相应的功能。本次作业相较于前两次作业较为简单,难度主要在于读懂JML规格和实现性能的优化。
正文
一、实现规格所采取的设计策略
在实现前我一般会先阅读指导书上的简要介绍,优先实现异常类。实现异常类之后由简到难实现各个类,三次均在最后实现network类。
在具体到类的实现时,我会先大致阅读一遍这个类各个方法的规格,确定成员变量所用的容器,然后按顺序逐步实现各个方法。
在实现单个方法时,首先确定normal_behavior和exceptional_behavior各自的requires,然后根据其规定弄懂其功能和最终需要的\result,选择已知性能最佳的方式进行实现。
二、基于JML规格的测试方法和策略
采用Junit 单元测试来对代码正确性进行测试。方法如下:
- 根据JML的前置条件设置对象的状态
- 调用方法并检查后置条件是否满足
- 测试数据量较小的边界情况和所有逻辑分支
测试过程中可把方法分为三类分别进行。
-
具有分支的方法
对JML所规定的所有分支进行测试,使用Assert判断分支的运行结果与预期结果是否一致。
-
抛出异常的方法
除了测试普通的行为之外,还要使用@Test(expected=Exception.class)注解对抛出异常的功能进行测试。
-
维护中间变量的方法
需要对所有影响该中间变量的方法进行针对性的测试。
三、容器选择和使用的经验
- 有对应关系的量,且无顺序需求,如person和id之间,id和value之间,一般采用HashMap,方便查找和算法设计,降低查找时的时间复杂度。
- 没有对应关系的量,或者不需要通过一个属性寻找另一个对应属性的量,特别是有顺序规定的,一般采用ArrayList,方便根据标号寻找对象和顺序遍历。
四、本单元易出现的性能问题
第一单元作业
本单元易出现性能问题的其实就是queryBlockSum函数对应的qbs指令,很多同学用dfs或bfs来实现isCircle方法,复杂度已经为\(O(n^2)\),再用isCircle方法实现queryBlockSum方法,很容易导致CPU超时。
我在这里采用了并查集的方式实现,通过find和join两个方法构建关系网络和实现路径压缩,维护一个qbs变量来得到块的个数,通过判断两人是否有同一个根节点来判断isCircle,降低了复杂度,最终没有出现性能问题。
第二单元作业
本单元易出现性能问题的方法为getAgeVar和queryValueSum。
-
getAgeVar
很多同学使用了双重循环来实现这一函数,使得复杂度为\(O(n^2)\),最终导致超时。我在这里使用了\(Var(x)=E(x^2)-E(x)^2\)这一公式,在addPerson和delPerson时维护\(E(x^2)\)和\(E(x)^2\)两个中间变量,将时间复杂度降为了\(O(1)\),从而解决了这一问题。
-
queryGroupValueSum
通过设置一个valueSum中间变量进行维护来解决性能问题。在addPerson和delPerson以及addRelation三个方法分别对中间变量来进行对应的维护工作。
第三单元作业
本单元易出现性能问题的方法为sendIndirectMessage方法,这个方法要实现最短路径的搜索,一般采用普通的dijkstra算法复杂度为\(O(n^2)\)可能会导致超时。
我在这里采用了堆优化的dijkstra算法,使用java的priorityQueue进行操作,降低了时间复杂度,没有出现性能问题。
五、作业架构设计
整体的架构均严格按照JML规格进行实现。
- MyPerson类相当于图的结点,负责储存Person的一系列信息和修改工作,同时设有acquaintance列表储存与点有关联的其他结点,相当于邻接表。
- MyGroup类负责构建小组,储存组的信息和组的维护工作。
- MyNetwork类是整个作业的核心,负责构建和维护整个图模型和传递信息,其中addPerson负责添加点,addRelation负责添加边,addToGroup负责将点进行分组。
- Message相关类负责各种信息的构建。
- 各种异常类负责构建异常。