BUAAOO Unit3博客作业
OO第三单元总结
撰写技术博客:
(1)总结分析自己实现规格所采取的设计策略
(2)结合课程内容,整理基于JML规格来设计测试的方法和策略
(3)总结分析容器选择和使用的经验
(4)针对本单元容易出现的性能问题,总结分析原因如果自己作业没有出 现,分析自己的设计为何可以避免
(5)梳理自己的作业架构设计,特别是图模型构建与维护策略
实现规格采取的设计策略
- 首先通读接口中规格的描述,根据方法规格分析类中属性应该用哪种容器实现,属性之间的关系能否综合起来(如以键值对的形式),确定类内数据管理结构
- 实现所有的接口方法,阅读方法规格,将规格种每一个语句转换成自然语言,理解方法的作用并实现
- 对于类内的方法,综合利用构建中间层次、采用复杂度低的算法的方法来进行方法的优化,降低时间复杂度
- 在实现的过程中先从基础的类入手(异常类、Person类),逐步向上层实现,根据需要增加类内方法来方便进行数据管理和查询。
在三次作业中我们只需要根据规格的表述来进行实现,但是这并不是一个简单的事情,我们需要理解规格描述的语义,并且尽可能一次性理解清楚,尽量减少修改,否则很容易因为修改过程中思虑不周导致很多方法中遗留bug,说多了都是泪啊😢
基于JML规格的测试方法
Junit是专门用来进行单元测试的框架
在理解方法规格的基础上,根据规格构造测试数据,并利用Junit插件编写测试类进行单元测试
Junit单元测试代码编写小知识:
- 几个注解的语义:
@Test:测试方法,方法执行结果无论是正常执行和抛异常都与预期一致才通过,并且如果用timeout限制时间的话,只有执行时间在限制之内才能pass@Before:在每一个测试方法之前运行的代码段,所有测试方法的首部公共代码段@After:在每一个测试方法之后运行,同为所有测试方法的尾部公共代码段@BeforeClass:方法为静态方法(static 声明),在所有测试开始之前运行,与before不同,该方法只在所有测试方法运行之前运行一次@AfterClass:方法同为静态方法(static 声明),在所有测试结束之后运行,与after不同
- 检验结果的正确性采用assert断言来判断
本单元才开始采用单元测试的方法,最开始还以为Junit插件能根据规格自动生成测试用例来进行测试,一度认为插件安的有问题,咋还得我填。。阿巴阿巴。。偷懒未遂。。
除Junit单元测试之外,就是黑盒测试了。通过构造简单的测试用例,来检查调用过程中是否出现了问题,并通过跑从同学那里搜刮来的数据来进行正确性判断(跪谢)。
容器使用
-
HashMap
本单元使用的最多的就是HashMap了,Map由于是以键值对的方式进行数据存储,所以方便进行数据的映射和数据查找,并且HashMap对自由增删查改数据很友好,所以像Person类中的values,经常会根据personId来进行数值查找,所以采用HashMap<personId,value>来进行存储会更加方便。而且使用HashMap也能够方便地进行数据遍历,使用
.keySet()即可获得键的集合,使用.values()即可获得值的集合。 -
ArrayList
ArrayList适合利用数组下标来进行访问的数据。在本单元作业中ArrayList的使用很少,我在Person类中采用ArrayList来存储messages,由于我们需要根据插入顺序来对数据排序,所以采用ArrayList能够自然地对数据按照插入顺序排列,而且也可以避免多余内存占用。
private ArrayList<Message> messages; public void addMessage(Message message) { messages.add(0, message); } public List<Message> getReceivedMessages() { ArrayList<Message> temp = new ArrayList<>(); if (messages.size() <= 4) { temp.addAll(messages); } else { temp.add(0, messages.get(0)); temp.add(1, messages.get(1)); temp.add(2, messages.get(2)); temp.add(3, messages.get(3)); } return temp; } -
Queue
第三次作业使用堆优化的迪杰斯特拉算法的时候用到了PriorityQueue容器,它是基于优先级的优先级队列,根据构造队列时提供的Comparator进行排序。第一次使用这个容器,简单学习一下用法:
- add() / offer():向队列中插入元素
- poll():从队列中弹出队首元素,默认最小元素出列
性能问题分析
本单元后两次作业都出现了性能问题。
第一次作业考虑到isCircle()和queryBlockSum()可能会产生性能问题,所以为每一个Person节点增加了一个可达集属性:Reachable的list,这样就使得每一次add relation都会产生O(n)的复杂度,但是在方法isCircle()就只有O(1)的复杂度,queryBlockSum()方法有O(n)的复杂度。其实复杂度还是很高的,只是第一次作业房间里的xdm手下留情,没有hack我。。。
第二次作业由于没有采用并查集而是沿用第一次作业的方式,被卡了好几个样例;除此之外,还有Group中getAgeMean()方法为O(n)的复杂度,牵连getAgeVar()方法也是O(n)的复杂度,共同导致CPU TIME LIMIT ERROR。后将结构改为并查集实现,并为Group设置AgeSum属性,完成了bug修复。
第三次作业我吸取前两次的教训,请教了别人该用什么算法,所以使用了堆优化的迪杰斯特拉算法,复杂度为O((m+n)*logn),没有在这个点上被卡,如果采用最朴素的算法,复杂度可能达到O(n3),采用朴素迪杰斯特拉算法复杂度为O(n2)。但是第二次侥幸逃过的问题在这一次被疯狂攻击,qgvs使用了最简单的算法,有O(n^2)的复杂度,被多次hack。当时好像还注释了这里要改,忘了,害。
第三单元作业感觉上最简单,但是实际上做得最差,因为需要考虑算法上的问题,而这个是之前一直所忽略的,没有意识到算法的重要性,轻视了作业难度。同时这个单元也暴露了我在算法这个方面的问题,真的涉猎太少,并查集和堆优化的迪杰斯特拉都是第一次听说和使用,真的该去看看算法书了。
架构设计
第一次作业
这次作业没有复杂的层次结构,大体直接按照规格说明设计。值得一提的就是并查集结构了,虽然我在第二次作业中bug修复才更正。并查集对于查看节点的连通性有很大的遍历,在对节点添加关系的时候直接构建树结构来构建连通图,在插入节点时只需建立两个节点之间的关系就可以,如果这个树的父子关系确定,则在查询任意两节点是否可达时的复杂度为O(logn),而在查询的时候我们可以顺便进行路径压缩,将这个过程中查询到的节点都与这个树的根节点建立父子关系,这样查询就能够在原复杂度的基础上继续降低复杂度。
第二次作业
第二次作业沿用第一次的图架构,并在此基础上增添了Group类,其中有一些查询操作,所以这些查询操作的方法设计就是至关重要的,ageMean如果每次都从头用公式计算一次的话,则为O(n)复杂度,这自然不如每次增删Person时进行数据计算来得简单,还有ValueSum的计算,直接计算就是O(n^2)的复杂度,但是采用在线计算的方式,就可以降低,在增删人员、人员间关系的方法里会有O(n)的复杂度,但在查询时只有O(1)复杂度,所以在计算指令密集的之后就能够尽量避免超时的问题。Message类较为简单,没有复杂的结构,且对于Network中的Group和Message我都采用HashMap来进行管理。
第三次作业
第三次作业使用了层次化设计,对于Message有多种类别,建立层次化继承关系就是最简单的方法,这在规格中也有明显的提示。这次作业中我收获最大的就是堆优化的迪杰斯特拉算法了,我采用构建一个中间层次来进行最短加权路径的计算。这个图的结构直接利用Person的属性作为一张图来进行计算,在增删人员、人组关系时候的维护复杂度为O(1),在查询的时候需要动态构建一个优先队列。这里记录一下该算法的核心流程:
public int dijkstra(int id1, int id2) {
HashMap<Integer, Boolean> visit = new HashMap<>();
PriorityQueue<Node> pq = new PriorityQueue<>(new Node());
pq.add(new Node(id1, 0));
while (!pq.isEmpty()) {
Node t = pq.poll();
int id = t.getId();
int valueSum = t.getValueSum();
if (id == id2) {
return valueSum;
}
if (visit.get(id) == null) {
visit.put(id, true);
Person person = getPerson(id);
HashMap<Integer, Integer> value = ((MyPerson) person).getValues();
for (int i : value.keySet()) {
if (visit.get(i) == null) {
pq.add(new Node(i, valueSum + value.get(i)));
}
}
}
}
return -1;
}
class Node implements Comparator<Node> {
private int id;
private int valueSum;
public Node() {}
public Node(int node, int cost) {
this.id = node;
this.valueSum = cost;
}
@Override
public int compare(Node node1, Node node2) {
return node1.valueSum - node2.valueSum;
}
public int getId() { return id; }
public int getValueSum() { return valueSum; }
}

浙公网安备 33010602011771号