BUAA_OO_第三单元作业总结

Posted Hankitle

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BUAA_OO_第三单元作业总结相关的知识,希望对你有一定的参考价值。

BUAA_OO_第三单元作业总结

总结分析自己实现规格所采取的设计策略

本单元作业由于设计架构已经基本规定好,只需要实现部分接口的功能即可,所以历次作业的实现几乎相同,都遵循以下几个步骤:

  1. 阅读指导书,先大体梳理一遍需要实现的是哪些部分
  2. 阅读JML整体规格,关注当前接口对应的类需要维护哪些属性以及该类的规格大小,并大体阅读一下有哪些方法以选择适当的容器
  3. 细读JML规格,先实现一些容易实现的类,如PersonMessage和异常类,再实现GroupNetwork等相对较难实现的类

测试方法

白盒测试

由于笔者个人对于JUnit测试方法的理解与使用并不到位,难以写出十分合理的单元测试框架。

因此笔者只根据JML规格对个别相对重要的方法编写了相应的测试方法进行了功能性弱测,测试了queryBlockSumisCirclequeryGroupAgeVardeleteColdEmojisendIndirectMessage这几个方法。

黑盒测试

笔者本次单元测试主要采用了黑盒测试方法。

一是通过随机生成样例然后对拍以进行测试,将自己程序的输出与他人程序输出进行对比以确保没有出现功能性错误。

二是有针对性地构造了一部分极端数据,用以验证自己的程序不会出现CTLE的情况。

容器选择和使用

本次作业中笔者主要使用了ArrayListHashMap这两种容器,在实现并查集的堆优化时,还使用了优先队列PriorityQueue

Group

  • ArrayList<Person> persons,用于存储该Group中包含的人。考虑到接口中对外提供的方法没有过多的查找需求,且在addPersondelPerson中用于缓存valueSum的属性的修改不可避免的要对persons全体元素做循环访问,故此处决定采用ArrayList

Person

  • HashMap<Integer, Integer> acquaintance,前一个存人的id,后一个存这两人之间边的value。考虑到需要在Person类中存储其所认识的人与这两人之间的value,故将其二者合并直接使用HashMap存储,这样存储则类似于图存储方法中的邻接表,即存储当前结点与当前结点和其他节点之间的边。且该容器对外配置了getAcquaintance方法,利于NetWork对整个图进行访问。
  • ArrayList<Message> messages,由于其内置方法getReceivedMessages仅获取至多四条消息,且对接收到的信息有顺序要求,故采用ArrayList进行存储。

Network

  • HashMap<Integer, Person> persons,用于存储该Network中所包含的人。由于后续方法中有多数需要利用id来访问对应的Person对象,故采用HashMap
  • HashMap<Integer, Group> groups,用于存储该Network中所包含的群。
  • HashMap<Integer, Message> messages,用于存储该Network中所包含的消息。
  • HashMap<Integer, Integer> emojiList,用于存储该Network中所包含的表情以及其对应的热度。由于后续方法中有部分需要根据表情id来对热度进行修改的方法,故采用HashMap
  • HashMap<Integer, Integer> father,用于实现并查集。用于存储该节点与该节点的父亲节点。
  • HashMap<Integer, Integer>,用于实现并查集的按质合并优化。存储该节点与以该节点为根节点的树的深度。
  • PriorityQueue<Node> queue,其中Node为自己创建的继承了Comparable接口的类,用于实现Dijkstra算法的堆优化。

性能分析

isCircle

该方法用于查询两节点之间是否联通。如果采用深度遍历或广度遍历算法,在处理当前图为一条长链,且查询节点为最两端的结点时,运行时间则会比较漫长。

故此处采用并查集,在加关系时将两不同块进行合并,且内置find方法用于查找当前节点所对应的根节点,且在查找过程中实现路径压缩优化。使用时直接返回find(id1) == find(id2)即可。

queryBlockSum

该方法用于查询当前图中的连通块的数量,如果采用普通的遍历算法,则运行时间会更加漫长。

优化方法同上,采用并查集实现。查询时只需查询当前图中并查集的数量即可,复杂度可降低到O(n)(也可以在类中缓存block,将复杂度降低至O(1))。

getValueSum

求一个局部图中所有边的权重之和。

该方法如果直接按照规格中所采用的二重循环的方式,复杂度则会上升至O(n^2),当输入数据中包含很多qgvs指令时就会出现CTLE。

优化方法即内置属性valueSum,在加人删人时进行单次循环对valueSum进行修改。同时,也要注意在加关系时要对valueSum进行修改,故可以提供public方法addValue,当Network中加关系时便可调用addValue方法对valueSum进行修改。

getAgeMean/getAgeVar

求一个群内所有人的年龄的平均值与方差。

这两个方法可以直接按照规格中所采用的一重循环的方式,经本地测试应该也不会出现CTLE的情况。

当然也可以进行一些优化提升运行速度,可以内置属性存储年龄和与年龄的平方和。当在Group内进行加人或删人操作时可以直接对这两个属性进行修改。返回时利用数学公式稍加计算即可。

sendIndirectMessage

该方法内部需要查询两节点之间的最短路径。

经本地测试,直接采用常规的Dijkstra算法在强测数据规模下有一定概率会出现CTLE的情况。

优化方法即使用堆优化的Dijkstra算法。

在存储方面,新建Node类继承Comparable接口,用于存储某节点与其他结点之间的距离以及该节点的编号,并重写其中的compareTo方法。使用PriorityQueue来存储其中的结点。

大部分的操作与常规的Dijkstra算法相同,在获取当前最短路径对应的节点时只需要取出PriorityQueue的头节点即可,同时在存储新的路径时只需要将其直接压进优先队列中,并不需要判断新节点的编号是否已经在堆中存在。

作业架构分析

由于本次作业已经给出了总体架构,且对于所需要实现的类有及其清晰易懂的JML规格描述,因此在类的设计与实现上并不需要过多考虑。

图的构建与维护在实现规格所提供的方法的基础上,额外增加了并查集,用于实现对qci和qbs这两条指令的性能优化。

在个人实现代码中面向对象思想的体现,似乎只在于实现了一个并未在要求中的Node类,其次就是各个具体的Message的实现是通过继承自己实现的MyMessage类,利用super方法来简化子类实现。

以上是关于BUAA_OO_第三单元作业总结的主要内容,如果未能解决你的问题,请参考以下文章

BUAA_OO 第四单元总结——UML

BUAA_OO 第三单元总结

BUAA_OO_2021_Unit3_Summary

BUAA_OO 第二单元总结

BUAA_OO_第四单元

BUAA_OO_2020_Unit3_Summary