OO 第三单元总结
oo第三单元单元总结
设计策略
首先通读JML要求,辨析总共有几种输入与对应的处理,区分异常输入处理与正常输入处理。在实现时优先实现异常类,因为三次作业实现的异常类都非常相似,实现容易。然后实现异常输入处理,按JML顺序抛出各种异常,最后根据正常输入进行处理。由于数据条数的限制(10000条),因此o(n)复杂度的方法可以直接实现而无需考虑性能问题。只有复杂度大于o(n)的方法需要格外注意并采用方法进行优化,可以放到最后进行实现。因此整个实现过程就是:异常类——异常输入处理——复杂度不大于o(n)的方法实现——较复杂方法的实现——测试。
测试方法与策略
容器选择与使用经验
本次作业存储的数据Person,Message,Group等都具有唯一可供区分的ID,因此非常适合使用HashMap容器,以ID作为key进行索引,在查找,插入时都具有很高的效率。由于ID理论上不会重合,因此不会出现key值相同的问题,保证了数据一致性。ArrayList结构在需要频繁访问特定数据时不太合适,需要遍历容器,本次实验查询过程非常多,因此不宜使用。
性能分析
本单元作业可能出现性能问题的函数只有直接处理复杂度会大于o(n)的函数,因此数量不多,有第九次作业的isCircle(), queryBlockSum()方法;第十次作业的queryValueSum();第十一次作业的sendIndirctMessage()方法。
第九次作业
第九次作业的方法用于查询两个人是否间接或直接认识,将所有相互之间间接认识的人视为一个集合,则该问题转换为两人是否处于同一集合中的问题。解决元素是否在这种关系集合中的问题非常适合使用并查集来解决,由于不存在删除人员的方法,因此可以放心的进行路径压缩而无需担忧改变网状结构的问题。实行路径压缩之后多次查询两人之间的关系的复杂度也下降为o(1)。并查集的实现采用经典方法,在MyPerson类中增加fatherId属性表示其父节点,在Network中增加getFatherId()方法递归获得节点父节点并实现路径压缩。方法queryBlockSum()获取人员的集合数,可通过判别有多少不同的顶层父节点来判定存在多少集合,因此复杂度为o(n)。
第十次作业
第十次作业的queryValueSum()方法需要获取组内所有人之间好感度的总和,如果采用二重循环的方法实现由为o(n^2)的复杂度,必然会超时,而如果简单采用一个Integer存储之前总和,在修改组内两人关系后再查询需要o(n^2)代价来更新Integer的值,倘若进行查询,增关系,查询……的方式也会超时。因此采用一个HashMap<Integer,Integer>来存储不同ID的人贡献的ValueSum的值,倘若两人关系更新了,只需要修改HashMap中的两个值即可获得新的值,每个值的更新复杂度均为o(n)。并且增加一个全局HashMap<Integer,Integer> Relation表示某ID的人关系被修改的次数,即假设修改了5号Person与7号Person的关系,则为Relation.get(5) 与Relation.get(7)的值加一。Relation采用单例模式实现,使得各Group能很方便获取。Group内自己存储一个HashMap<Integer, Integer> localRelation结构,在增加Person时设置该Person的localRelation值为当前Relation值,并且为ValueSum的各个元素增加与新增者的关系。当删除人时,为ValueSum减少与删除者的关系。当查询时,如果localRelation的值与当前Relation的对应值相等,则直接采用当地缓存的值,如果不相等,则重新计算该Person与其他人员的关系值并更新localRelation的值。如此每条add_to_group()需要增加o(n)复杂度,每条del_from_group()需要增加o(n)复杂度,每条在add_relation()至多导致o(n)的复杂度,而且倘若只添加关系而不查询,则不需要更新本地valueSum的值,因此不会随Group实例的增加而导致query_value_sum代价增加。整体复杂度在o(n)左右,因此在10000条数据下不会超时。
第十一次作业
第十一次作业的sengIndirectMessage()方法需要查询两人之间的“最短路径”。人与人之前边的长度由两人之间的关系决定,采用普通迪杰斯特拉算法为o(n^2)复杂度,且对退化为链型的关系网效率相对低。因此进行改进,每次仅遍历与当前有直接关系并且还未找到最短路径的节点。设置一个HashMap<Integer,Integer> finded表示ID对应的人的最短路径是否找到,增加HashMap<Integer, Integer> distance表示到ID节点的当前最短距离。其中distance为每次遍历寻找最小值的集合,需要在每一次查找时动态更新。初始化为与初始节点直接相连的节点,查找到最近节点时,将与最近节点有直接关联的节点加入distance中,并且将最近节点移出distance中。如此在处理链状关系网时具有很高的效率,对于关系复杂的集合复杂度接近o(n^2)。但是这种算法存在明显缺陷(后来发现的),如果起始节点与其他节点都有关系,就会一下子将所有节点拉入最小距离的讨论范畴,妥妥的o(n^2)。从同学那了解到,可以通过使用小顶堆存储节点的距离,每次更新值时将更新的距离存入小顶堆(不需要删除之前的值)。倘若更新的距离小于之前的距离,自然会在之前的值上方,会在之后更优先被取出来。设置一个数组表示该节点的最短路径是否已经找到,如果从堆顶取出的元素最短路径未被找到,即为所求结果,否则舍弃。这样在小顶堆中插入与查找时间复杂度为o(logn),整体时间复杂度不超过o(nlogn),可完美达成性能要求。
作业架构
第九次作业中的图结构需求主要体现在实现方法isCircle()与queryBlockSum()上。采用并查集简化了图结构,简化后的图为单向有向图,从子节点指向父节点,相互间有直接或间接关系的人会通过有向边最终汇聚到一个顶层父节点。每次查询父节点时递归查询并进行将路径上的所有节点的父节点直接指向根节点。在多次查询后该图结构深度会逐渐接近2.
第十次作业中的图结构主要体现在queryValueSum()中,查询组内两两之间关系的总和即查询group自己中所有带权边权重和。图结构通过Person类中的关系人员来保持,并未额外构建图结构。
第十一次作业的图结构来源于sendIndirectMessage()中,查询间接相连的两节点之间的最短路径,使用的图结构仍是Person关系网,通过Person的人员关系来维持。

浙公网安备 33010602011771号