OO第三单元总结博客
Posted 梁兆基
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OO第三单元总结博客相关的知识,希望对你有一定的参考价值。
目录
一、前言
二、程序分析
- 规格理解
- 策略(容器选择等)与时间复杂度
- debug与历次commit分析
- 每次作业小结
三、互测自测策略
- 对拍机
- JUnit单元测试
- OpenJML
- JMLUnitNG——自动化的JUnit
四、心得体会
一、前言
本单元主题是基于规格的层次化设计,要求我们理解JML语法,根据给定的JML规格完成设计需求,并且满足一定的性能要求,同时掌握简单的基于规格的测试方法。规格将设计与实现分离,控制了架构的复杂性;规格可以让开发人员准确理解一个类的功能,一个方法的行为;规格通过逻辑方式来验证代码实现的正确性,这是保证代码实现正确功能之本;规格还是开展测试设计的依据,只有理解规格,才能对类、方法和接口进行完备的测试。三次作业以实现方法规格、异常处理为起点,逐步增量扩展功能,增加方法规格和类型规格,最后引入类型层次下的规格,训练复杂应用场景下规格的代码补全,锻炼基于规格层次化设计能力。
二、程序分析
第一次作业
-
规格理解
将社交模拟系统看成一个无向连通图,Person类管理了点的信息和与其他点的关系,
queryValue
相当于给出边的权。isLinked
判断两个点是否有边。NetWork
类管理了整个图(网络),维护所有点和点的关系,其中决定本次实验复杂度的方法为isCircle
,即判断两点是否连通。以及queryBlockSum
,即计算该图连通分量的个数。还有一个容易出bug的地方便是queryNameRank
,在不抛异常时其值至少为1。 -
策略与时间复杂度
- 容器
- 第一次由于没有出现大量遍历操作所以用了
ArrayList
管理点Person
(实际上还是有不少用到contains
的方法的),只有Person
的邻接点集合friend
属性需要同时管理两个变量所以选择HashMap
。
- 第一次由于没有出现大量遍历操作所以用了
- 方法
isCircle
和queryBlockSum
最终都是要判断两个点是否连通,可以将这个功能单独抽离出来成为一个方法,这里大概有几个实现的方向:DFS,BFS,并查集。递归实现DFS会造成嵌套过深爆栈的问题。个人在第一次使用了队列实现BFS查找。
- 时间复杂度
- 由于规格要求Person记录邻接点使用邻接表,BFS查找时间复杂度O(V+E),其中查找每个顶点所用时间O(V),访问某顶点邻接点所用时间O(E)。
- 容器
-
debug与历次commit分析
- ver1-ver2 没有理解
isCircle
的含义,以为是判断两点间存在边 - ver4 将递归DFS改成BFS
- debug 对拍发现
queryNameRank
返回值有误,分析规格时少看了个+1
。
- ver1-ver2 没有理解
-
小结
- 刚开始写的时候只是死板地严格按照规格的方式照猫画虎,这样虽然能保证正确性但在性能上完全无法保证不超时。所以写到一半就换为实现抽象功能的思路。
第二次作业
- 规格理解
- 本次作业新增了两个类
Group
和Message
。新增的功能主要在于发送消息(私聊和群发),以及计算Group
中的年龄期望和方差。
- 本次作业新增了两个类
- 策略与时间复杂度
- 容器
- 意识到遍历带来的性能损失,将所有用到遍历的容器替换为
HashMap
,Java1.8以后HashMap
采用位桶+链表+红黑树实现,可以将根据键值搜索复杂度降至O(1)
- 意识到遍历带来的性能损失,将所有用到遍历的容器替换为
- 方法
- 意识到BFS带来的高时间复杂度,改用并查集,以
ArrayList
和HashSet
实现Quick Union - 计算期望和方差时,在调用方法时计算会造成高时间复杂度。于是在
Person
加入Group
等任何会对容器产生更改操作时维护当前期望和方差,调用方法只需要返回当前期望和方差的值
- 意识到BFS带来的高时间复杂度,改用并查集,以
- 时间复杂度
- 计算方差和期望的部分在部分情况可以将复杂度降到O(1)。当然,实际维护过程中有时会用到遍历故最大时间复杂度为O(n)
- 并查集合并复杂度O(1),查询复杂度O(n)
- 容器
- debug与历次commit分析
- ver2 方差计算公式正负号写反 容器全部换为
HashMap
- ver4 更正if-else逻辑不严谨的地方 remove方法传参传错为value
- Dsu 更改为并查集算法
- ver2 方差计算公式正负号写反 容器全部换为
- 小结
- 第一次作业留下了BFS时间复杂度过高的遗留问题最终改用并查集解决。同时期望和方差的随时维护也再次印证了对规格要彻底地理解其功能,并且采取最简单的方法实现这一原则。
第三次作业
- 规格理解
- 本次作业为信息扩展了类型——红包、通知、表情。而功能上增加了求两点最短路径、间接发送消息(只要两点连通即可发送)以及根据表情热度淘汰使用次数最少表情。
- 策略与时间复杂度
- 容器
- 沿用第二次作业容器,新增
HashMap
表情容器同时管理表情和热度(这里如果严格按照规格用两个容器会增加开销)。
- 沿用第二次作业容器,新增
- 方法
- 主要的时间开销在求最短路径上。本次作业使用堆优化的Djikstra算法,使用优先队列实现。需要额外写一个
Path
类表示所求路径,管理路径长度和起始点的Id,并且重写compareTo
来实现优先队列中各路径的按长度升序排列。该算法的特点是:每次优先队列入队都会保持升序,也就是说离得近的点先出队遍历找到当前最短路径,使用优先队列会使寻找新的最短路径时间大大减少(稀疏图情形)。同时若前面的点已经更改到到后面的点的最短路径,跳过该路径的遍历,减少开销。 - 间接发送消息 注意各种信息分别处理
- 删除最少使用表情 注意不能使用嵌套遍历,遍历最好使用用迭代器或者foreach遍历entrySet。在
Messages
种删除表情时可以利用表情是否在已删除表情的表情管理容器存在来判断是否需要删除。
- 主要的时间开销在求最短路径上。本次作业使用堆优化的Djikstra算法,使用优先队列实现。需要额外写一个
- 时间复杂度
- 堆优化Djikstra算法在稀疏图情形下复杂度O(nlogn),logn为插入(排序)开销。
- 删除表情时间复杂度O(n)。两次遍历。
- 容器
- debug与历次commit分析
-
对拍debug:
MyMessage
的equals
写的不严谨(判断是不是Message而不是判断class严格相等);MyPath
中id和lenth传参反了;money计算(没-1及给别人加钱没去自己) -
debug 3.0 更改了删除
Messages
中表情的方式。原来需要对删除id进行存储再进行遍历。增加了开销。
-
- 小结
- 本次作业一方面考察了类层次的规格设计,子类新增规格不能与父类相悖,另一方面依然要求注意算法的选择。
互测自测策略
对拍机
- 本单元实际过程中主要使用对拍机debug及hack。起初使用纯随机数据,发现甚至难以验证实现正确性,于是结合了一定的人工构造(先添加大量点及边,对
Group
进行反复修改操作然后进行随机) - hack情况
- 第一单元:部分数据点有超时状况
- 第二单元:
sendMessage
后没有删除信息;抛出异常的id记录错了;没有考虑group<1111
的限制情况(这会导致被错误地加上该点,若后面用到该点会报错) - 第三单元:未hack出bug
JUnit单元测试
- 尝试使用Junit编写单元测试,但由于作业方法调用关系复杂,简单的测试难以完备覆盖,所以仅进行简单功能的验证。
- 步骤
- 安装插件
- 右键类选择
Go To
中的Test
- 编写测试代码
- 运行查看结果
OpenJML
- 用于验证规格编写的正确性,不过可能由于版本问题某个导致一些本来规格正确的地方报错,需要动手修改规格,实用性不强。
- 安装
- 运行指令 java -jar .\\openjml.jar -exec .\\z3-4.7.1.exe -esc .\\src*.java
JMLUnitNG——自动化的JUnit
- 用于自动生成测试样例并进行单元测试。必须保证规格严格正确,而且多数数据为边界数据,不好进行压力测试,同样实用度不高。
- 步骤
- 安装
- 运行指令$ javac -cp jmlunitng.jar src/*.java
四、心得体会
- 实操规格层次化设计,理解JML基础语法,并且领会这种标准在设计上的先进性(严格规范)
- 在对规格理解表面含义的基础上,抽象出所定义的功能,并且在符合规格的同时,灵活运用各种技巧简化过程
- 对图相关计算的算法进行了一次复习,加强自我对于复杂度上的要求。
- 对Java容器的遍历性能有了更深入的理解。
- 在对图进行修改增删操作时,动态维护图的属性,这样可以大大降低复杂度。
- 尝试用多种方式对规格实现正确性进行测试,了解了各种方法的具体操作中的利弊。
以上是关于OO第三单元总结博客的主要内容,如果未能解决你的问题,请参考以下文章