OO课程第三单元总结

本单元的三次作业,围绕JML规格的主题展开。如果能够耐着性子将每个函数给出的JML规格通读一遍,就能够较为轻松地写出大致正确的代码。本单元不仅在总体难度上较前两个单元更加简单,而且没有性能分的限制,因此可以说是十分友好的一个单元。

设计策略分析

  • 第一次作业

    在该次作业中,我们所需要实现的大部分函数都较为简单。
    由于NetworkPerson类中有较多的查询,因此可以很自然地想到使用HashMap或者HashSet两个容器来存储各种元素,来减少查询所需的时间。比如规格中Network类中的people数组可以为一个键为id,值为Person类的HashMap;Person类中的acquaintance数组可以为一个HashSet。
    本次作业中需要注意的函数为Network类中的isCircle()queryBlockSum()两个函数。学过图论的我们都知道可以使用DFS或者BFS来进行计算,也有同学也可能会使用并查集来进一步提高效率。由于本次作业中数据量较小,DFS与BFS已经能够在时间允许的范围内完成工作,此外考虑到后续作业可能会出现人员的删减(实际上并没有),并查集会不好维护,因此我采用了DFS的方法来实现这两个函数。

  • 第二次作业

    第二次作业中在上次作业的基础上加入了GroupMessage类,同时加入了较多的异常,虽然工作量较大,但难度上没有较大提升。
    该次作业中,我依然是使用HashMap与HashSet作为主要的容器。规格中的Group类中的people数组、Network类中的groups和messages数组都可以使用HashMap来实现,在查询时会有较好的性能。不过也有例外,由于Person类中的messages数组需要取出最近存入的四条消息,因此我采用了ArrayList来实现,在功能上会类似与栈的数据结构。
    本次作业中需要注意的函数为Network类中的queryGroupValueSum()函数。如果处理不好,那么即使使用了Hashmap,单独调用这一函数仍然可能会有O(n2×logm)的时间复杂度,面对极端数据仍然会有超时的风险。

  • 第三次作业

    第三次作业中在上次作业的基础上加入了继承Message的三个子类,加入了两个的异常与一些函数。相较于前两次作业,该次作业对于细节上的正确性要求进一步提升,需要大家仔细阅读JML规格。
    该次作业中,对于大多数方法,依旧可以安心使用HashMap与HashSet作为容器,此处就不再赘述。
    本次作业中较有难度的函数为Network类中的sendIndirectMessage()函数。该函数相当于寻找图中两点的最短路径。学过图论的我们都知道可以使用Dijkstra算法来进行计算,不过如何选择合适的数据结果同样对该算法的效率有影响。该次作业中数据强度较弱,CPU允许运行时间也较长,因此简单使用HashMap和HashSet其实就已经足够了。在这里我向同学学习了堆优化的Dijkstra算法,使用了PriorityQueue容器方便寻找距离的最小值,进一步提高了效率。

测试方法简述

虽然课程组推荐学生使用基于JML规格的方法来进行测试,但使用该种方法进行测试的相关资料较少,而JUnit插件的使用也较为繁琐,因此我没有使用该方法来进行测试。
鉴于本单元的作业不同于上单元的电梯,对数据输入的时间没有要求,且输出唯一,同时,本单元作业的数据生成较为简单,随机数据与极端数据都较容易构造,因此使用对拍的方式进行测试更为简单方便。本单元中我没有在强测与互测中发现BUG,正是因为对程序进行了较多的本地测试。

容器选择和使用的经验

我们的OO课程已经到了第三单元,经历了前两个单元的腥风血雨,现在再总结容器选择使用的经验实际上会显得有些本末倒置。在这三个单元的学习中,我使用过ArrayList、HashMap、HashSet、TreeSet、PriorityQueue这五种容器。实际上我对这些容器的使用还称不上熟练,所以我就简单地一边看菜鸟教程地谈谈。

  • ArrayList

    ArrayList,翻译成中文就是我们最熟悉不过的数组。不过在Java中,这一容器实际上是动态数组。我个人的理解是,一旦在数组中删除某个元素,数组中位于该元素序号后方的元素都会往前整体地移动,填补因删除导致的空缺。因此在遍历删除ArrayList时,需要注意i的大小,推荐使用倒序的方式来进行遍历删除。
    尽管ArrayList使用起来较为简单,但由于其查询时会有O(n)的时间复杂度,在很多情况下都会有较差的效率,因此要谨慎使用。

  • HashMap

    HashMap可以说是我在Java学习中使用得最多的一类容器。单次查询的时间复杂度为O(logn),在大量查询时有很好的效率,删除元素时也会很方便,也同样可以遍历。不过虽然好用方便,但在HashMap在遍历删除多个元素时会常常报错,需要引入流或者迭代器,显得比较麻烦。此外,HashMap使用时有时需要对元素的HashCode()方法进行重写,这也有一定的讲究。

  • HashSet

    HashSet与HashMap十分相似,但是没有键。由于不能根据键进行查询,因此就业面也较HashMap狭窄一些,一般在一些没必要有键的情况下使用。使用时也会需要对元素的HashCode()方法进行重写。

  • TreeSet

    使用TreeSet前需要对元素的CompareTo()函数进行重写。TreeSet是一个有序列,可以较为方便地取出某一组元素中的最大或最小值。需要注意,TreeSet不能直接遍历,需要引入迭代器。

  • PriorityQueue

    PriorityQueue为优先队列,使用该容器时也需要对元素的CompareTo()函数进行重写。该容器同时具有TreeSet和Queue的特点,在使用上更为方便。

性能问题分析

在本单元的作业中,我没有出现过性能上的问题。这一方面是因为我在本地构造了强度尚可的数据进行测试,也同样是因为在实现JML规格时慎重选择相应容器,尽量使用时间复杂度较低的方法进行处理。

作业架构设计

由于三次作业中,新加入的类与方法都没有对上一次的规格造成较大的影响,因此这里直接给出第三次作业的类图。
image
其中,只有MyStruct类为我自己实现的额外的类,其余类都是按照相应规格要求所实现的。MyStruct类为迪杰斯特拉算法中PriorityQueue容器的元素类型,实现了CompareTo()函数以使得优先队列能够正常工作。
图模型构建与维护策略上,其实并不需要太多需要注意的地方。如果能够正常实现规格中的方法,图的结构就很自然地构建了。单个Person类作为结点,以HashMap的结构实现Acquaintance,从而与其他Person相连。由于不需要删除关系与结点,因此也不需要耗费太多精力去维护。

总结

本单元主要围绕JML规格的主题展开。一开始接触JML规格的时候,确实会感到这一规格的繁琐,不过读多了时候也就熟练了。只要认真阅读JML规格,理解函数的功能,就可以较为简单地实现函数,有一些在做填空题的感觉。本单元的作业难度较前两个单元下降了很多,涉及的知识也较为基础,适合巩固对Java容器的认识。

posted @ 2021-06-01 21:03  Synotp  阅读(60)  评论(0)    收藏  举报