填写规格的顺序
先完成异常类
因为异常类相对来说实现比较简单所以选择先完成异常类
再写非Network
的普通类,最后写Network
类
总的来说,Network
类是实现最复杂的类,它里面也基本上使用了非Network
的其他类,所以先写非Network
类,有助于对代码的理解,也能在写Network
时更得心应手。
选择合适的容器
详细见第3点
选择性能较好的算法
在isCircle
在queryblockSum
中计算连通分量的个数时,我使用了数据结构课中使用过的dfs
算法,边遍历边删除结点
在sim
中我采用dijistra
算法计算最短路径。
形式化验证
对照JML规格进行形式化验证。我是先根据JML规格把规格“翻译”成自然语言,然后检查自己实现的代码是不是这样。因为有的时候写着写着就可能“多干一步”、或者“少干一步”,出现难以发现的bug,所以我认为对照JML进行验证算是不错的查找bug方法。
Junit测试
对于Network
的函数,单独测试其功能正确性。
因为我不太会写自动评测机,所以就只能手动构造数据了,基本上把能想到的一些情况都测了,但还有一个注意的地方就是,我有的时候是直接跑而不是debug模式,所以有些时候虽然结果是正确的,但可能多删了一些东西、或者少删了一些东西,在后面测试的时候才发现这个问题。
在这个单元里,我主要使用的容器是HashMap,在要求是List的规格中使用了LinkedList
我选择用HashMap来存储MyNetwork中的people、groups、messages等,选择HashMap的原因是因为这样便可将“取/存”操作变为复杂度为O(1),提高性能。
对于像queryReceivedMessage
这种指令,其JML规格要求使用List容器来存放每个人所接收到的Message,因为有很多的“添加”操作,所以我采用了LinkedList而不是ArrayList这种容器。
总而言之,我是能HashMap
就HashMap
,不能的话再根据情况选择。
遍历一个HashMap
HashMap<Integer, String> oneMap = new HashMap<>();
Iterator iter = oneMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
//取出当前的key
int key = (Integer) entry.getKey();
//取出当前的value
String value = (String) entry.getValue();
}
边遍历边增删
(与上面给的遍历过程类似,在遍历中使用remove()方法删除)
HashMap<Integer, String> oneMap = new HashMap<>();
Iterator iter = oneMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
iter.remove();
}
对queryBlockSum
我最初是没有优化的,只是采用之前数据结构课使用过的BFS算法,结果后来在第二次作业中因为qbs超时了,我才发现其实对于所有的查询操作,是不改变Network中blockSum的值,只有addPerson
和addRelation
操作才会改变blockSum的值,所以我当每一次调用addPerson
和addRelation
时就会将use
置0。当调用qbs
时,如果use
为0,调用BFS计算新的blockSum值,并将其存入lastBlockSum
中,并将use
置1;如果use
为1,则直接返回lastBlockSum
即可。
对queryGroupValueSum
、queryGroupAgeMean
类指令
可以在MyGroup
中增加两个变量valueSum
和ageMean
,当每次对group进行addPerson
和delPerson
时对valueSum和ageMean进行维护。需要注意的是,当Network进行addRelation
操作时,需要遍历groups,当添加关系的两个人都在group中时需要增加该group的ValueSum。
对SendIndirectMessage
我采用了dijistra
算法,优化的话,可以使用堆排序的dijistra
算法(虽然我没有这么实现),我将dijistra
包装成一个类,因为一次travel可以计算目标顶点到其他所有顶点的最短路径,所有我在Dijistra
类中使用HashMapminWeight
把每个节点到其他节点的最短路径长度都保存下来,跟queryBlockSum
类似,当每次addRelation
后就将useWeight
置0,当调用sim
时,如果useWeight
为0,则清空minweight
里保存的最短路径长度,调用dijistra.travel
计算新的最短路径值,并将结果存入minWeight
中,将useWeight
置1;如果useWeight
为1,那么看useWeight中是否计算过iddao其他节点的最短路径,如果计算过则直接从useWeight
中取出并返回即可,如果没有计算过,则调用travel
计算。
除了官方要实现的类我创建了Dijistra
类和TravelBfs
,一个是计算最短路径,一个用来计算两个人是否连通。
TravelBfs
private HashMap<Integer, MyPerson> people = new HashMap<>();//id, Person
private HashMap<Integer, LinkedList<MyPerson>> linkPeople = new HashMap<>();
private HashMap<Integer, Integer> visit = new HashMap<>();
其中,people
、linkPeople
与Network
中的定义相同,在每次调用时需要将当前Network
中的值赋给TravelBfs
。visit
是标记此节点是否被访问过。
Dijistra
private HashMap<Integer, MyPerson> people = new HashMap<>();//id, Person
private HashMap<Integer, LinkedList<MyPerson>> linkPeople = new HashMap<>();
private HashMap<Integer, Integer> wfound = new HashMap<>();
private HashMap<Integer,
HashMap<Integer, Integer>> minWeight = new HashMap<>();//<id, <id, weight>>
其中,people
、linkPeople
与Network
中的定义相同,在每次调用时需要将当前Network
中的值赋给Dijistra
。minWeight
是用来保存计算过的最小路径权重,格式为<id1, <id2, weight>>保存了每一个结点到其他所有结点的最短权重。
我会根据是否有“添加关系”,“所要找的人是否已经算过其最短路径”来判断是否重新计算最短路径还是直接返回之前计算过的最短路径。需要注意的是在“添加关系”后,之前计算过的最短路径可能都不再是当前情况下的“最短路径”,所以此时就要将minWeight
清零。
原文:https://www.cnblogs.com/duoduola/p/14827523.html