OO第三单元总结
本单元主要针对JML规格进行学习以及考察,相较于以往两个单元,轻松许多,对JML规格的了解也深入许多。
第一次作业
设计策略
本次作业主要需要实现Person
类、Network
类、四个异常类以及对应的计数器。
其中Person
类中,比较关键的方法为isLinked与queryValue,由于第一次作业采用的是ArrayList
,故使用循环遍历搜索解决,后两次作业改为HashMap
后便可直接调用相应的内置方法进行处理。
Network
类中实现规格主要需要注意正常情况与异常情况条件的判断,其关键的方法有iscircle和queryBlockSum,分别用来判断社交网络的图中两个人是否在同一个连通块中以及计算图中连通块的个数,本人是通过深度优先搜索算法(未优化,时间复杂度为O(n2))进行实现,且qbs中直接调用iscircle,这也最终导致了超时。
异常类的实现,主要是实现一个静态增长的计数器,在Count
类分别实现不同异常的静态计数变量以及对应的增加方法,当异常进行print时,对应的计数变量进行增加,然后按格式输出即可。传入异常的id,则是根据JML规格的描述进行传递,如MyPersonIdNotFoundException
类,即根据不包含的id传入并进行输出即可。
测试方法
由于第一次作业的规格大致较为简单,验证正确性方面,先是对比JML规格与代码,查找低级错误,后针对较为复杂的方法如qbs进行测试,此时个人是采用自己手动构造,并使用python的对拍程序进行对拍测试。由于个人的不细心,测试不到位,导致强测中最终深度优先算法出现问题。
容器选择
第一次个人对于性能没有过于关注,自认为按照JML规格写,应该都不会超时,所以容器大部分采用了ArrayList
,这也导致许多方法中存在着大量遍历操作。且此容器使用起来比较麻烦,不够灵活,后续作业便舍弃了此容器。
bug分析
此次作业主要出现的bug在于深度优先算法的实现出现问题,且未进行算法的优化,也导致出现超时问题。后将深度优先算法改为广度优先算法,且qbs中不再直接调用iscircle,而是采用广度优先算法的思想进行处理优化,最终修复了bug。
第二次作业
本次作业最终需要实现一个社交关系模拟系统。可以通过各类输入指令来进行数据的增删查改等交互。
设计策略
本次作业主要需要实现Person
类、Network
类、Group
类、Message
类、八个异常类以及对应的计数器。
Person
、Group
、Message
类中,规格的实现较为简单,只需要注意一点,Message
类存在两种构造方法,而此对应两种不同的类型。
Network
类中实现规格主要需要注意正常情况与异常情况条件的判断,其新增的关键的方法有sendMessage,其规格很长,但主要的实现却并不难,分Message
类型进行处理即可。对于连通块的判断,在此次作业中又一次进行了大规模修改,该为使用并查集的方法进行查找,原理如下图。即如果两个人之间存在联系,便把一个人的''祖先''设为另一个人的“祖先”(使用递归),最终在判断连通与否以及连通块的数量时,便可节省大量时间。
异常类的实现,由于第一次作业已经完成整体的设计逻辑,再增加异常类变得十分简单,依葫芦画瓢即可。
测试方法
验证正确性方面,先是对比JML规格与代码,查找低级错误,后与同寝室的人进行正确性与性能的对拍测试,此时采用的数据是随机构造。但由于随机构造数据未达到覆盖性测试,导致强测中某个异常出现问题。
容器选择
此次作业绝大部分都采用了HashMap
的容器结构,一般都是设立键值为人的id,并对之前的大部分方法进行了修改重写。修改为HashMap
后,使用极为便利,如由一个id得到具体的人,直接使用get(id)
便可解决,而不需要再遍历搜索。
bug分析
本次bug主要在于在addMessage
方法中MyEqualPersonIdException
异常的id传入错误,确实是属于低级错误,修改后问题解决。
此次作业主要出现的bug在于深度优先算法的实现出现问题,且未进行算法的优化,也导致出现超时问题。后将深度优先算法改为广度优先算法,且qbs中不再直接调用iscircle,而是采用广度优先算法的思想进行处理优化,最终修复了bug。
第三次作业
本次作业最终需要实现一个社交关系模拟系统。可以通过各类输入指令来进行数据的增删查改等交互。
设计策略
本次作业主要需要实现Person
类、Network
类、Group
类、Message
类、EmojiMessage
类、NoticeMessage
类、RedEnvelopeMessage
类、十个异常类以及对应的计数器。其中有关message的均需要实现具体的接口。
Person
、Group
、Message
类中,规格的实现较为简单,只需要注意一点,Message
相关的类存在两种构造方法,而此对应两种不同的类型。
Network
类中实现规格主要需要注意正常情况与异常情况条件的判断,其新增修改的关键的方法有sendMessage、deleteColdEmoji、sendIndirectMessage,这其中sendMessage与上次类似,只是需要针对不同的信息类型,进行不同的处理即可,而deleteColdEmoji在进行容器修改时,需要注意用到临时容器存储,不能直接删。而sendIndirectMessage是这次最为麻烦的方法,其需要实现图中两点之间最短路径的查询,个人一开始使用的是未优化的迪杰特斯拉算法(时间复杂度O(n2)),后根据测试发现可能存在超时的风险,则修改为使用堆优化(使用java内置的PriorityQueue)的迪杰特斯拉算法,大大降低了运算时间。
异常类的实现,也按照以往操作进行实现即可。
测试方法
由于第三次作业的规格以及类的复杂度大大提升,以及测试指令的多样,故测试方面采用了他人的构造数据(感谢CH大佬)并与其他多人进行对拍,由于测试样例的强度具有保障,所以最终强测与互测都未出现bug。
容器选择
此次作业新增的容器,仍采用的是HashMap
,做到能用此的便用此容器。
bug分析
此次作业由于测试覆盖广、强度高,未出现bug。
本单元性能问题
本单元出现的性能问题可能出现在以下几个方面:
容器的选择
由于个人一开始使用的ArrayList
,在第二次作业的自我测试中发现,某些方法的运行极为缓慢,后发现是ArrayList
的大量遍历搜索占用时间。之后便将能修改为HashMap
的地方都修改了,避免了性能问题。
连通块判断与计算
个人这方面由于一开始未考虑性能问题,直接使用的是深度优先搜寻,导致最终超时了一个点。后来进行修改,先是改为优化后的广度优先搜索算法,发现还是可能出现超时的问题,最终改为并查集的算法,方才解决性能的问题。
人的价值总和的计算(getValueSum)
一开始使用的是简单的循环计算,虽然在第二次作业没有被卡,但由于我室友在互测中被卡,所以在第三次作业中重新设计,把ValueSum设为一个动态变化的量,当有人加入或者有关系加入时,更新ValueSum,查询时只需查询此变量即可,大大降低了时间复杂度。
图中两人最短路径的查询
一开始使用的是未优化的迪杰特斯拉算法,后在自测中发现,有些测试点将大大超时,因此搜索资料修改为利用堆优化的迪杰特斯拉算法,并使用java内置的类PriorityQueue
进行设计算法,最终可避免性能问题。
架构设计
此为第三次作业的UML类图
由于官方的代码已经把规格写好了,且具体的方法多少、类的多少已经定好,所以按照官方的规格进行实现即可,这样实现的作业架构应当是比较好的。
个人实现与维护的主要是并查集以及无向图。
并查集主要用于判断连通以及计算连通块的个数。而无向图则是整体的图架构,使用HashMap
容器进行一系列操作。
感谢总结
此单元作业相比以往两个单元来说,可以说简单了不止一点半点,但在学习过程中也发现,虽然简单,但一旦出现一点小差错与失误,将会错很多点。因此此单元的自我测试极为重要。且此单元对于算法的检验也比之前两个单元更为明显,比如并查集解决连通块、堆优化的迪杰特斯拉算法等等。