OO第三单元总结
Posted adwur
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OO第三单元总结相关的知识,希望对你有一定的参考价值。
OO 第三单元总结
1 实现规格所采取策略
由于本单元的三次作业都已经提供了各个类的规格,在实现规格时只需要理解清楚这个规格想让我们做什么(它在表达什么),这个规格的逻辑关系、约束条件是怎么样的,这个方法最终要实现什么效果等,理清后实现规格就显得十分得心应手了。
在实现比较复杂的方法规格时我一般首先实现规格内的异常处理行为(如果有的话),这样做的原因是异常处理的规格一般比较简单,能够在比较短的时间内理解清楚并实现。同时,实现异常处理一般使用 if-else if
的代码语句,这样一来当将所有的异常处理实现后,剩下的 else
就是 normal behavior
了,整个方法实现起来也比较有层次感。
2 基于 JML 规格测试
基于 JML 规格的测试工具主要有 OpenJml、JMLUnitNG、JUnit等。下面对其进行简单介绍。
2.1 OpenJML
OpenJML 主要是对 JML 注释的正确性进行检查,包括类型检查、变量可见性和可写性等。通过命令行使用 OpenJml 时,可以通过 -check
参数(缺省)指定类型检查。类型检查只能确保 JML 注释的格式正确,为了对规格内容进行检查,需要使用 -esc
参数(静态检查需要指定 prover
)。使用 -rac
选项可以进行运行时检查。命令如下所示:
openjml [-check] options files
2.2 JMLUnitNG
该工具可以根据规格自动生成测试样例,主要用于测试边界数据。若规格中需要传入一个 int
变量,就会生成 INT_MAX
,0,INT_MIN
等数据进行测试。
2.3 JUnit
JUnit 是 Java 语言的单元测试框架,我们可以通过自己编写测试代码对源文件进行自动测试,具体测试方法为:创建一个 TestCase
的子类;写一个测试方法断言期望的结果;写一个 suite()
方法,它会使用反射动态的创建一个包含所有的 testXxxx
方法的测试套件;写一个 main()
方法以文本运行器的方式进行测试;最后运行测试。
3 容器选择和使用
由于本单元中 Person
、Group
、Message
类都带有其唯一的 id
,且 emoji
也拥有 id
,因此在 NetWork
中各种元素集合都选择使用 HashMap
作为容器。同时,在 NetWork
类中需要实现压缩路径的并查集算法,这个数据结构也使用了 HashMap
,key 为 某一 Person 的 id,value 为 该 Person 对应集合的头节点。HashMap
不仅符合 key-value
一一对应的特性,同时查找、添加、删除元素等操作都比较简单。
在 Person
类中的 messages
集合需要顺序读取或添加元素,因此使用 ArrayList
作为容器。
在第三次作业查找最短路径中使用了堆优化的 Dijikstra 算法,因此使用 PriorityQueue
作为容器。
4 性能问题
在本单元第一次作业中,容易出现的性能问题为 isCircle
以及 queryBlockNum
方法的时间复杂度。使用 dfs 或 bfs 在强测中不会出现问题,但 qbs 方法中不应该按照规格描述中使用双循环,而应该写一个查找连通块的新算法(该算法也可以是 dfs 或 bfs 实现)。最佳方法应该为实现并查集算法,时间复杂度会大大降低。
本单元第二次作业容易出现的性能问题为 valueSum、ageVar 查询的时间复杂度。在查询 valueSum 时,若直接采用双循环方法,就会超过限制时间出错。为此降低时间复杂度的方法有在 group 加人或删除人时对此变量进行更新,查询时只有 O(1) 复杂度。查询 ageVar 时则先将 ageMean 算出,再进行循环。
本单元第三次作业容易出现的性能问题是最短路径的计算,也即 sim 指令的实现。如果使用普通的 Dijikstra 算法,时间复杂度为 O(n^2);因此需要使用堆优化的 Dijikstra 算法,将时间复杂度降为 O(mlogn)。
5 架构设计
5.1 第一次作业
isCircle
使用 dfs 方法,queryBlockSum
使用双循环加 dfs 方法,在互测中被卡了一下时间。在 bug 修复中 queryBlockSum
使用 dfs 方法直接查找连通图个数。
5.2 第二次作业
isCircle
使用路径压缩的并查集算法,queryBlockSum
方法不变(在强测中被卡),在 Group 类中添加并维护 sumAge 变量,用于查询 ageMean 以及 ageVar。查询 valueSum 方法中使用双循环,在互测中被卡。在 bug 修复中 queryBlockSum
方法转为使用并查集,在 Group 类中添加 sumValue 变量,在加人、减人、添加关系时进行维护,并且修改了查询 valueSum 方法,使其时间复杂度降为 O(1)。
5.3 第三次作业
为了实现堆优化 Dijkstra 算法,新建两个类,一个是 PersonEntry 类,用于记录与某个人的路径距离,并实现 Comparable 接口中的 compareTo 方法。另一个类用来实现堆优化 Dijkstra 算法。该类中存储着一个图,采用 HashMap<Person, ArrayList<PersonEntry>
的形式进行存储,通过 Network 中 的加人、加关系操作进行维护和修改。
异常类的实现设置了一个静态变量存储异常发生的次数,设置了一个静态 HashMap 存储对应 id 触发异常的次数。
最终类图如下所示(异常类被隐藏):
源文件中的各个类所属包如下所示:
6 心得体会
本单元作业总体上比前两单元作业简单了不少,只要理解 JML规格的含义,选择正确方式实现,就不会出现太大的问题。同时,这个单元的几次作业让我明白在实现规格时不能完全按照规格的描述对方法进行实现,需要在满足规格限制的情况下自行考虑时间复杂度、空间复杂度、程序性能等其他因素,自行设计并维护一些变量,实现一个好的方法。这个单元的作业也是我第一次使用包,将各个类进行一定的分类,也让我对层次化设计有了新的理解。总的来说,这个单元的几次作业让我对程序细节的实现有了新的理解和体会,也提高了我的算法能力。
7 参考
以上是关于OO第三单元总结的主要内容,如果未能解决你的问题,请参考以下文章