OO第三单元总结
前言
(这次总结还是一份踩坑记录,作业难度降下来了,坑倒是一个没少踩,继续亡羊补牢,从失败中学习ing)
这单元学习了JML相关,这三次作业也是根据JML的规格实现程序。
单元重点
-
条框本身:JML本身的语法看一下手册不难看懂,但难的是几百行JML全部符合
-
细心 :说是品质,更像是种能力。在这个阶段更感受到自己的粗心,再准确些可以保证避免低级错误。
-
-
条框之下的实现过程:JML只规定最终结果的限制,在过程中如何写出保证程序效率的同时优雅的实现,就是看程序员品质的时候了。
-
保证程序效率 注意时间复杂度 不照搬JML 找算法实现
-
进行充分测试
-
一、实现规格的设计策略
0.总结
简单函数照搬JML即可 注意(sum/forall/exist ...)里嵌套的括号,别看串
特别是JML里嵌套两层for,时间复杂度达到O(n^2)的自己写算法 写完确保满足规格
1.第一次作业
第一次作业实现 Person 类和简单社交关系的模拟和查询
-
简单函数只需用HashMap建立映射关系,就可以把getxxx()的时间复杂度最小化为O(1)
-
需要注意的是is_circle 和 query_block_sum 这两个函数。
-
踩性能坑:我第一次写的时候用了朴素的邻接矩阵DFS实现is_circle,邻接矩阵的DFS 时间复杂度O(N^2)。对比邻接表的DFS时间复杂度O(E+N) ,导致强测TLE。 数据结构没学好,当时压根就没想起邻接表,果然欠下的都要还。。。
-
改进:并查集算法,add_person()时新增block,add_relation时判断是否融合block。
-
眼瞎踩坑:看错分号,把(forall ...)里面的一大串理解成了两句话,结果意义完全不一样。事后反思,还是对JML理解太浅,分开的两句应该是两个ensures,并且通过基本的逻辑判断很容易发现不符合函数名称。
-
2.第二次作业
第二次作业增加Group 和 Message 类,许多query方法。
-
简单方法还是照搬JML,实现上容器尽量用HashMap保证效率,只需细心即可。
-
需要注意的是各种query方法,照搬JML会出大问题。
-
踩性能坑:各种query方法照搬JML,那是写了一个个for循环,每次查询都要遍历一遍也没想有啥不对,这要是个复读机来直接累死。
-
改进:采用记忆存储,维护一个变量用来计当前值,无新增查询时直接return,有新增时增减记忆存储里的值更新。
-
3.第三次作业
第三次作业增加了EmojiMessage、NoticeMessage 和RedEnvelopeMessage实现了完整的简单社交网络,并且增加了send_indirect_message( )方法,需要实现查找最短路径,涉及到图的知识。
-
简单方法细心读JML即可
-
需要注意的是查找最短路径的算法
-
踩性能坑:写了朴素的Dijkstra算法,查找每个结点对应的最短距离需要O(n^2),导致TLE。
-
改进:堆优化Dijkstra算法,把时间复杂度O(n^2)降到O(elogn)。采用java自带的PriorityQueue构造小顶堆。原来查找结点对应的最短距离需要O(n)遍历,堆优化后直接取出堆顶最小值大幅降低时间复杂度。
-
一个之前没查出来的wa原因:还是缺少测试的锅。第二次的qgvs的记忆存储法其实写错了,过bug修复后没再测然后这次继续升天。
-
二、基于JML规格来设计测试的方法和策略
百果必有因,强测升天都是测试不行 ——by 我自己
测试方法课程组推荐的Junit,经过我的了解后发现,测试代码还是需要自己根据对JML的理解写。这样一是对复杂的方法太麻烦,二是觉得达不到目的,如果对JML本身就理解错了,那么如何能写出正确的测试代码呢?
另一个方法是对拍。由于我对py一知半解,尝试搭对拍机的时候卡了一个玄学报错,面向百度debug失败后就躺了,说到底还是太懒了,看其他同学大都采用对拍策略效果很好,问就是后悔。
懒蛋的悲剧结果,就是我大都是手动构造的数据,效果不佳也是意料之中。
三、分析容器选择和使用的经验
-
HashMap
本次作业的容器我大都使用了HashMap,因为它在查找上效率很高为O(1),同时有很多内置方法方便增删使用
-
ArrayList
用不到HashMap的地方我使用了ArrayList。在使用上有个坑点,容易忽略内置方法的复杂度,需要关注源码的实现。
举例来说,ArrayList.contains( xxx) 和 HashMap.contansKey(xxx) ,写出来都是一行但复杂度一个是O(n)相当于遍历,一个是O(1)。如果是HashMap<Integer,Group>存储Group,ArrayList<Person>存储组中人员的话,遍历所有组查找Person实际的时间复杂度是O(n^2)。不如牺牲一点空间换时间,将所属Group加入Person中。
四、性能问题
见(一)部分的分析。
五、图模型构建与维护策略
这三次作业中实际用图模型的,只有第三次作业求最短路径时用到。下面进行分析。
-
构建:自己建立edge类,存储<to结点,value> 注意须重写compareTo;Node类,存储<结点,ArrayList<edge>> 。此处还是用了邻接矩阵存储图,用邻接表效率更高。
-
维护: 使用 PriorityQueue存储图的原始边与松弛之后的边,使用visited[] 记录结点是否被访问,来避免对PriorityQueue的大幅删除。
浙公网安备 33010602011771号