整体上,首先根据要求及理解实现所有的异常类,随后实现Person
、Group
、Message
这类主要由属性及简单查询方法构成的类,最后实现Network
这类需要结合规格设计并修改原有类的较为复杂的类。
对于一般的查询方法利用HashMap
的key
值直接查询,如getMoney()
;对于需要计算更新的查询值进行缓存处理,如getValueSum()
;对于查询时时间复杂度高的操作进行特殊处理,如queryBlockSum()
,具体在五介绍。
最后,在现有实现的基础上对于部分方法的具体实现进行修改。
严格按照规格进行设计。检查时应该主要针对于规格实现的逻辑以及规格实现的细节问题。在测试时可以采用对拍的方式进行测试。
尽管按照JML设计规格正确性容易得到保证,但是譬如自行化简了方差计算的公式、对规格理解出错均导致本单元出现了较为严重的问题。
容器选择主要基于对于使用目的的考虑。
首先,本单元作业中存在着大量插入及查询操作,在规格实现中大多使用了Hashmap或者Hashset来存储信息,如groups
信息的存储、circles
的存储等。
Hashmap
记录每一个组中所有的Person_id
,并在查询到目标group_id后再在groups中取出目标group。在实现堆优化的Dijkstra算法时,使用java中PriorityQueue。由于优先级队列的头部是基于比较器的排序的最小元素,当轮询队列时利用poll()
方法返回头部元素,避免了循环取出距离最小值所带来的时间损耗,具体在四
中介绍。
在第二次作业中由于容器使用遇到一个bug,错误的将ArrayList的remove方法当做Hashset的remove方法使用,导致发生数组越界异常。(悲)
queryValueSum
()Group
中的queryValueSum
方法需要对组内每一个成语与其他成员之间的关系进行查询。而每次查询时都重新计算或对于同一段关系重复查询,都会造成时间浪费。因此本单元在实现时,使用缓存的策略实现。这种方法在测试中没有出现性能问题。
addPerson()
时查询其与现有组内成员的value值,并更新valueSum
。addRelation()
时对同时有两人的组进行valueSum
的更新。isCircle()
isCircle()
以及queryBlockSum()
方法,需要查询两个人是否能够在能够通过关系联系起来,考虑到每次遍历查询并不现实,因此采用并查集的思想,使用circles
保存并记录关系网络中的结点。这种方法在测试中没有出现性能问题。private final LinkedHashMap<Integer, HashSet<Integer>> circles
= new LinkedHashMap<>();
addPerson()
方法中为增加<Person_id, new Hashset()<>>
,即每一个circle
以一个Person_id
为键值,并在value
中加入该Person_id
作为成员。addRelation()
方法中合并两个结点所在的circle
。由此,查找BlockSum
时只需输出circles.size()
,以及在最短路径查找时也可以便捷的查找出起始点与终止点所在无向图的结点成员。sendIndirectMessage
()朴素Dijkstra适用于稠密图的最短路径问题,此时边数(即关系数)远远大于点数(即人数),采用进行n次循环,每次循环再遍历n个点确定一个仍未确定的最短距离的点钟距离源点最近距离的点,之后再用新的点进行更新。其时间复杂度为 $$O(n^2)$$。
由于我使用的是上述没有进行堆优化的Dijkstra算法,因此TLE了。
在本单元作业的背景下,关系数数量相对于人数并不多,因此朴素Dijkstra算法在此时就会出现超时的情况,因此在debug阶段采用堆优化的Dijkstra算法,解决朴素算法中循环所有的点以寻找最小路径点所造成的时间消耗,使用小根堆的思想用优先队列维护“最小的点”,从而降低时间复杂度到 $$O(m*logn)$$,$m$为边数,$n$为点数。
使用优先队列PriorityQueue实现堆优化的Dijkstra算法,由于优先级队列的头部是基于比较器的排序的最小元素,当轮询队列时利用poll()
方法返回头部元素,避免了循环取出距离最小值所带来的时间损耗,从而解决了TLE的问题。
MyNetwork
类的实现。其中,包括三中提到的circles,还有便于查找所添加的groupPeople,以及为实现最短路径查找所设计的graph。对于上文未曾提到的两点进行介绍。1. 记录在同一Group中的Person
private final LinkedHashMap<Integer, Group> groups = new LinkedHashMap<>();
private final LinkedHashMap<Integer, HashSet<Integer>> groupPeople = new LinkedHashMap<>();
设计中使用groups
记录所有的Group
信息。
addGroup()
方法中添加<group_id, group>
进行维护。addToGroup()
方法中向相应的Group
内添加Person
。另外,使用GroupPeople
记录同Group
中的Person
的id
值。
Group
内已有两个Person
发生addRelation()
操作时会影响到Group
中valueSum
的值,因此同时记录Group
内Person
的id
值便于进行查找。2. 记录与Person为Linked的关系网络图——graph
private final LinkedHashMap<Integer, LinkedHashMap<Integer, Integer>> graph
= new LinkedHashMap<>();
graph
记录图信息
addPerson()
方法中为每一个Person
增加<Person_id, <Person_id, 0>>
的结点。addRelation()
方法中为两个Person
添加<Person, <otherPerson_id, value>>
,从而实现对图的维护。? 实际上,通过单元测试反映出,尽管本单元整体难度低,但是实现的效果或者说最终成绩却比前两个单元差。第一次作业出现了容器使用错误的bug,第二次作业因为各种bug喜提强测0分,第三次作业因为粗心敲错方法以及使用的最短路径查询方法效率低,成绩也惨不忍睹。
? 在实现规格时,一方面我们需要严格的遵照要求实现,一方面也要考虑到实际的效率需求。与此同时,对于测试、检查的忽视也会直接的导致本单元的悲惨战况。
? 总之,本单元劫后余生的感悟是规格的实现、随后的测试与优化都很重要。当然,对于实现,最重要的永远是正确性。
原文:https://www.cnblogs.com/Happy-Huan/p/14833138.html