OO第三单元作业总结

一、自己实现规格所采取的设计策略

​ 在起初刚学习完JML规格语法的时候,由于对JML规格不够熟练,我选择了让自己的代码与规格代码基本保持一致来完成作业。意思是,JML使用数组实现,我也会使用数组实现;JML使用两层for循环实现,我也会使用两层for循环实现。并且,我选择了每读一个方法的JML,就完成一个方法。

​ 显然,这样的策略不会让我产生结果错误,但是问题也很显然——如果某个方法使用了双层循环嵌套,那根据算法复杂度分析,我们最高可能会达到O(n^3)的时间复杂度(其实达不到),显然,针对5000组的互测数据和10000组的强测数据,可能会造成TLE。随着这个问题的发现,我意识到,我不能完全按照JML写代码,我们需要优化。

​ 于是,在第二、三次作业中,我选择了先读完部分JML,比较好实现的类我会先完成;对于复杂的类,我会选择分步实现,如果发现复杂度可能会为O(n^2)的方法,我会对其做上标记,提醒自己这个方法可能会造成TLE,一方面是告诉自己要小心,一方面是可以针对这个方法构造数据来hack。

​ 就这样,我通过这样的策略完成了这单元的作业。

二、基于JML规格来设计测试的方法和策略

​ 说实话,本单元我并没有使用Junit等工具测试,而是依然使用评测机生成随机数据,通过对拍完成测试。

三、总结分析容器的选择和使用的经验

​ 最开始,我选择的是数组。但很显然,数组的缺点很明显,一个是大小不好确定,这一点可以通过使用ArrayList完善;另一个是查找的复杂度是O(n),这一点影响很大,让我放弃了数组和ArrayList的使用,选择了可以O(1)查询的HashMap。

​ 而对于Map的选择,中途我和我的同学还讨论过HashMap与TreeMap之间的选择。由于HashMap可能会有很多冲突,导致退化为O(n)复杂度,而TreeMap是红黑树实现,复杂度稳定在O(logn),所以,我们讨论了这次作业哪个容器更加优秀。通过网上搜索资料,得到以下数据:

​ 所以,我们最终选择了HashMap。

​ 由于要找出联通块的个数,我使用HashSet保存联通块的某个特征量,来得到连通块个数。但是HashSet的插入复杂度也不低,但我觉得影响不大。

四、性能问题的分析与解决

​ 由于数据组数最大是5000到10000,所以我们需要控制每个方法总的复杂度在O(nlogn)以内。所以,我们只需要解决规格中复杂度比这个复杂度高的方法即可。

1.第一次作业

​ 我认为第一次作业主要是让我们熟悉JML规格,主要的优化是将数组转化为可以O(1)查找的HashMap。

​ 其中复杂度最高的,一个是query_circle,一个是query_block_sum。

​ 这两个方法,要实现的分别是得到两个人是否处于一个关系网中,和连通块的个数,都是O(n²)以上的复杂度。为了实现这两个方法,我们可以使用并查集算法,构造一个关系网。那么,前者的复杂度就是O(1),最高可达O(n);后者复杂度是O(n)。而实现这个算法,最重要的部分是维护这个并查集,这个我们后面再说。

2.第二次作业

​ 本次作业主要有以下方法需要优化。

​ query_group_age_var:这个方法需要我们得到方差,求平均值的复杂度是O(n),所以求方差的复杂度可能会很高,那么我们可以通过维护一个age总和来解决。得到平均值只需要计算年龄总和/人数,复杂度O(1)。计算方差就用普通方法,复杂度O(n)。

​ query_group_value_sum:从规格来看,这个方法的复杂度很高。我们可以通过直接维护value_sum变量来解决这个问题。查询复杂度为O(1),我们便解决的这个问题。

3.第三次作业

​ send_indirect_message:显然,这个方法需要我们求两人之间的最短路长度,我们可以使用Dijkstra算法优化,将复杂度降至O(nlogn)。

五、作业架构设计与维护策略

1.架构设计

​ 由于是迭代开发,我们每次作业只需要考虑新增的方法即可。由于整体架构JML已经给出,我们要做的就是局部最优,即每个方法都做到最优,就可以使得整体最优。当然,有的方法想要达到最优,还需要配合其他方法进行数据维护,而维护十分重要。

2.维护策略

​ 对于第一次作业,我们需要维护一个并查集,涉及的操作有add_person和add_relation。对于前者,我们只需要在HashMap中添加<id, id>这样的键值即可。对于后者,我们需要做并查集中的merge操作即可。这样,我们就完成了并查集的维护。

​ 对于第二次作业,我们需要维护age_sum和value_sum。对于第一个变量,我们需要在添加删除人时修改变量即可。对于第二个变量,需要考虑的是,我们在addPerson时,需要判断这个人和group里其他人是否有关系,如果有,需要对变量进行修改;在add_relation时,我们需要判断这两个人是否同时在某一个group中,如果是,需要对变量进行修改;delPerson时,与addPerson同理,需要进行维护。维护复杂度为O(n)。

​ 对于第三次作业,我们需要维护Dijkstra算法中的边和顶点。对于顶点,我们只需要在add_person时插入顶点id即可。对于边,我们需要在add_relation时,添加正反两条边,构成无向图即可。

六、BUG分析

​ 在完成作业后,我通过测试发现了很多bug。好在我在中测截止前发现并修复了所有BUG,没有出现严重的错误。三次作业中,我都有一些由于疏忽导致的错误,如下文所述。

1.第一次作业

​ 通过并查集计算连通块个数时,我会将每个节点的根节点放入一个集合当中,最后计算集合的长度,就可以知道连通块的个数。但在放入根节点的时候,我只是放入了某个节点的父节点,而未使用find()方法放入根节点,出现了一个bug。

2.第二次作业

​ 计算方差时,我未考虑group中人数为0的情况,导致触发了异常。

​ 在动态维护value_sum时,我忘记考虑add_relation指令也需要进行维护。

​ group的人数上限应为1111人,我未增加限制。

3.第三次作业

​ 起初我以为emojiId与其messageId是相同的,导致我在获取emojiId时都是用的messageId,出现了许多错误。

​ 在发送redEnvelopeMessage时,person1获得的钱应该*-1,即他的钱应该减少,而我忘记乘-1。

​ 在写dijkstra时,忘记使用visited进行标记,导致很小的图却需要进行大量次数的循环。

七、心得体会

​ 通过本单元的学习,我对JML规格有了更深的理解。同样,本单元我也阅读了很多代码,让我对面向对象有了更多认识。

​ 这个单元除了让我们认识了JML规格,还培养了我们对代码的分析能力。在读代码的时候,我们需要分析给出JML代码的复杂度,并用与JML代码效果相同的复杂度更低的代码进行实现。

​ 如果不进行部分算法优化,可能不会挂强测,但是肯定会在互测环节被hack,所以,这个单元在我看来,更像是一个单元的“算法课” ACM玩家狂喜 ,对算法不熟悉的同学可能会十分烦恼,比如没有听过学过并查集的同学。

​ 对于测试部分,由于指令集巨大,所以自动生成数据部分的工程量也巨大。检查正确性部分,我选择了对拍。最终,我和另外一位同学一起完成这个评测机和三次作业的测试部分。

​ 总的来说,这单元作业没有前两单元的复杂与困难,相对简单一些,也给了我们充足的时间完成和测试还有摸鱼以及写OS。

posted @ 2021-05-30 11:30  Eddie555  阅读(93)  评论(1)    收藏  举报