OO_BLOG_UNIT3

OO_U3_Blog

一、实现规格所采取的策略

  • 整体上,首先根据要求及理解实现所有的异常类,随后实现PersonGroupMessage这类主要由属性及简单查询方法构成的类,最后实现Network这类需要结合规格设计并修改原有类的较为复杂的类。

  • 对于一般的查询方法利用HashMapkey值直接查询,如getMoney();对于需要计算更新的查询值进行缓存处理,如getValueSum();对于查询时时间复杂度高的操作进行特殊处理,如queryBlockSum(),具体在介绍。

  • 最后,在现有实现的基础上对于部分方法的具体实现进行修改。

二、测试的方法与策略

  • 严格按照规格进行设计。检查时应该主要针对于规格实现的逻辑以及规格实现的细节问题。在测试时可以采用对拍的方式进行测试。

  • 尽管按照JML设计规格正确性容易得到保证,但是譬如自行化简了方差计算的公式、对规格理解出错均导致本单元出现了较为严重的问题。

三、容器选择与使用经验

  • 容器选择主要基于对于使用目的的考虑。

  • 首先,本单元作业中存在着大量插入及查询操作,在规格实现中大多使用了Hashmap或者Hashset来存储信息,如groups信息的存储、circles的存储等。

    • 在具体使用中,考虑到查询效率的问题,使用容器同时存储一份信息,即牺牲空间来换取时间效率,如此使用又一Hashmap记录每一个组中所有的Person_id,并在查询到目标group_id后再在groups中取出目标group。
  • 在实现堆优化的Dijkstra算法时,使用java中PriorityQueue。由于优先级队列的头部是基于比较器的排序的最小元素,当轮询队列时利用poll()方法返回头部元素,避免了循环取出距离最小值所带来的时间损耗,具体在中介绍。

  • 在第二次作业中由于容器使用遇到一个bug,错误的将ArrayList的remove方法当做Hashset的remove方法使用,导致发生数组越界异常。(悲)

四、总结分析性能相关问题

1. 求组中值总和的问题——queryValueSum()

  • 本单元中Group中的queryValueSum方法需要对组内每一个成语与其他成员之间的关系进行查询。而每次查询时都重新计算或对于同一段关系重复查询,都会造成时间浪费。因此本单元在实现时,使用缓存的策略实现。这种方法在测试中没有出现性能问题。
    1. addPerson()时查询其与现有组内成员的value值,并更新valueSum
    2. addRelation()时对同时有两人的组进行valueSum的更新。

2. 求封闭网络的数量问题——isCircle()

  • 本单元中isCircle()以及queryBlockSum()方法,需要查询两个人是否能够在能够通过关系联系起来,考虑到每次遍历查询并不现实,因此采用并查集的思想,使用circles保存并记录关系网络中的结点。这种方法在测试中没有出现性能问题。
private final LinkedHashMap<Integer, HashSet<Integer>> circles 
    = new LinkedHashMap<>();
  1. 初始结点:在addPerson()方法中为增加<Person_id, new Hashset()<>>,即每一个circle以一个Person_id为键值,并在value中加入该Person_id作为成员。
  2. 后期维护:在addRelation()方法中合并两个结点所在的circle。由此,查找BlockSum时只需输出circles.size(),以及在最短路径查找时也可以便捷的查找出起始点与终止点所在无向图的结点成员。

3. 求最短路径问题——sendIndirectMessage()

  • 朴素Dijkstra适用于稠密图的最短路径问题,此时边数(即关系数)远远大于点数(即人数),采用进行n次循环,每次循环再遍历n个点确定一个仍未确定的最短距离的点钟距离源点最近距离的点,之后再用新的点进行更新。其时间复杂度为 $$O(n^2)$$。

  • 由于我使用的是上述没有进行堆优化的Dijkstra算法,因此TLE了。

  • 在本单元作业的背景下,关系数数量相对于人数并不多,因此朴素Dijkstra算法在此时就会出现超时的情况,因此在debug阶段采用堆优化的Dijkstra算法,解决朴素算法中循环所有的点以寻找最小路径点所造成的时间消耗,使用小根堆的思想用优先队列维护“最小的点”,从而降低时间复杂度到 $$O(m*logn)$$,$m$为边数,$n$为点数。

  • 使用优先队列PriorityQueue实现堆优化的Dijkstra算法,由于优先级队列的头部是基于比较器的排序的最小元素,当轮询队列时利用poll()方法返回头部元素,避免了循环取出距离最小值所带来的时间损耗,从而解决了TLE的问题。

六、梳理自己的作业架构设计,特别是图模型构建与维护策略

  • 作业架构整体按照JML规格实现,根据效率需求进行设计的部分主要是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信息

    1. 初始结点:在addGroup()方法中添加<group_id, group>进行维护。
    2. 后期维护:在addToGroup()方法中向相应的Group内添加Person
  • 另外,使用GroupPeople记录同Group中的Personid

    1. 由于Group内已有两个Person发生addRelation()操作时会影响到GroupvalueSum的值,因此同时记录GroupPersonid值便于进行查找。

2. 记录与Person为Linked的关系网络图——graph

private final LinkedHashMap<Integer, LinkedHashMap<Integer, Integer>> graph
    = new LinkedHashMap<>();
  • 设计中使用graph记录图信息
    1. 初始结点:在addPerson()方法中为每一个Person增加<Person_id, <Person_id, 0>>的结点。
    2. 后期维护:在addRelation()方法中为两个Person添加<Person, <otherPerson_id, value>>,从而实现对图的维护。

五、单元总结

​ 实际上,通过单元测试反映出,尽管本单元整体难度低,但是实现的效果或者说最终成绩却比前两个单元差。第一次作业出现了容器使用错误的bug,第二次作业因为各种bug喜提强测0分,第三次作业因为粗心敲错方法以及使用的最短路径查询方法效率低,成绩也惨不忍睹。

​ 在实现规格时,一方面我们需要严格的遵照要求实现,一方面也要考虑到实际的效率需求。与此同时,对于测试、检查的忽视也会直接的导致本单元的悲惨战况。

​ 总之,本单元劫后余生的感悟是规格的实现、随后的测试与优化都很重要。当然,对于实现,最重要的永远是正确性。

posted @ 2021-05-31 20:31  Frida_h  阅读(100)  评论(0编辑  收藏  举报