BUAA OO 第三单元总结
由于本单元的三次作业是逐层递进的,故只对最后一次作业进行分析。
-
总结分析自己实现规格所采取的设计策略
本单元作业的设计基本同JML所给出的相同,只是对判断两点连通、求连通块个数、求组内成员的相关值以及求最短路径这几处进行了优化,具体的优化参照下文性能问题的部分。
-
结合课程内容,整理基于JML规格来设计测试的方法和策略
本次测试采用的是随机生成数据+与大佬对拍的方法。
-
总结分析容器选择和使用的经验
在这三次作业中,我在大多数时候都是使用的
ArrayList()来存放数据,而异常类的ID发生次数、Dijkstra的最短距离数组和访问标记数组以及EMOJI使用次数的存放中使用了Hashmap。在这三次作业过后,我认为我的选择出现了很大的失误,因为使用了
ArrayList()存放数据,其他函数中大量的ID查询操作会带来不必要的时间开销,因此我认为在这个程序中,涉及ID查询操作的数据都应该用Hashmap来存储。此外,为了方便查找边的权重,MyPerson中的accquaintance也应该用Hashmap。总结一下选择的经验:对于频繁根据非索引和非实值查找实值的数据,应该将用于查找的设为键值,建立哈希图。
本次容器的使用在大多数情况下仅涉及
ArrayList()的add()、contain()、remove()操作以及Hashmap的put()、containsKey()、clear()、remove()操作。在删除哈希图中元素时使用了迭代器。总结一下使用的经验:JAVA本身提供的方法最优,切记造轮子。
-
针对本单元容易出现的性能问题,总结分析原因如果自己作业没有出现,分析自己的设计为何可以避免
(1)判断图中任意两个点是否连通,计算连通图的个数
这个问题涉及的函数有两个
isCircle()以及queryBlockSum(),其中isCirle()是用于判断点的连通性,queryBlockSum()返回连通块的个数。对于
isCircle(),如果使用原始的dfs深度优先算法,该函数复杂度为O(n^2)。尽管这种方法可以通过中测和强测,互测时queryBlockSum()与仅仅很容易被其他人hack。因此,我在实现过程中使用了并查集,这种方法将复杂度降低至O(4)左右。并查集的核心是并和查两个函数:对于查,我记录了一个
father HashMap,当每个人加入社交网络时,将其自己作为father,每个人对应一个father,每个father为一个连通块的代表元,故而查询两个点是否连通,只需要查询两个点的father是否相同即可,注意查询时找到最根源的father并进行更新;对于并,我选择了路径压缩的方法,则当在两个点之间加边时,用一个点的father更新另一个点的father。对于
queryBlockSum(),简单通过isCircle()和双循环遍历每一条边,复杂度很高,在第二次的强测就会出现TLE的情况。而使用了并查集的方法之后,我们只需要查找有几个代表元,即father的value中有几个不同的值就能判断有几个连通块,这种方法复杂度只有O(n),相较于前文提到的方法已经好了很多。(2)计算每组中的方差、平均数以及所有边的权重的和
这个问题涉及的函数包括
MyGroup中的getValueSum()、getAgeMean()、getAgeVar()这三个函数(MyNetwork中的相关函数仅是调用了以上相应的3个函数,故而复杂度仅仅于前文提到的3个函数有关)。对于
MyGroup中的getValueSum()、getAgeMean()、getAgeVar()这三个函数,当输入的指令中存在大量查询时,可能会因为过多没必要的循环导致TLE(以上三个方法的复杂度依次为O(n^2)、O(n)、O(n))。我选择在类中设置相应的属性,在每次加入人的时候都对该值进行更新,这样一来每次查询的时候只需要直接对存储的值进行简单的计算。需要注意的是在
addPerson()、delPerson()和MyNetWork中的addRelation()对值进行更新。(3)寻找图中两点的最短路径
本次作业中,寻找最短路涉及的方法是
MyNetwork中的sendIndirectMessage()方法,使用的是堆优化Dijkstra算法。如果使用最简单的Dijkstra算法,其复杂度为O(n^2),查询起来有可能TLE。建立点和最短路径长度对应的
Hashmap和访问标记的Hashmap,每次调用时赋予其合适的初值(0x3f3f3f3f是记录最短路径的合适初值)。并建立如下图的Edge类来存放相应边的终点和权重。![]()
每次利用JAVA自带的优先队列
PriorityQueue<>()来获取每次最短的边。再通过正常的Dijkstra方法求出最短路即可。需要注意的是,为了进一步缩短运行时间,当我们遍历到了终点的时候就可以退出该程序而没有必要计算从起点出发的所有最短路。 -
梳理自己的作业架构设计,特别是图模型构建与维护策略
图模型
存储:
对于图中的每一个点,维护一个
accquaintance的ArrayList存放与这个点直接相连的其他点,并在value数组对应索引位置存放相应边的权重值。在MyNetwork类中维护一个存放了所有点的ArrayList() people,记录一个father HashMap用于实现并查集加点:
直接向
people中加入元素,并对Hashmap father进行维护,用于之后判断连通。加边:
对这条边的两个点的
accquaintance数组和value数组进行维护,并调用并查集方法join合并两个连通块。判断两个点是否连通:
在
isCircle()中调用并查集中的find()方法找到两个点所在连通块的代表元,若相同则返回true。获得连通块的个数:
返回
father中互不相同的value值的个数即可。寻找最短路径:
维护一个
dist Hashmap用于记录最短路,一个vis Hashmap用于标记是否访问,然后通过堆优化的Dijkstra算法进行最短路的查找。emoji相关
利用
Hashmap存储,键值为id,数据为该emoji被使用的次数。message、group相关
利用
ArrayList存储。异常类相关
如下图
![]()



浙公网安备 33010602011771号