BUAA_OO 第三单元总结
一.自测数据
本次作业我采用的自测策略是根据JML规格中给出的边界条件自造数据进行自测。例如本单元第一次作业的IsCircle函数,可以通过建立复杂图的方式验证算法本身及细节是否正确;第二次作业的queryLeastConnection函数可通过建立含复杂最小生成树的图进行自测;第三次作业的sendIndirectMessage函数可以建立权值差别过大、过小的图来进行自测。
当然,此方法弊端是有些JML规格的隐含条件可能会被忽略。例如有关Group大小不能超过1111的条件就被忽视,导致程序出现bug。
二.图的构建与维护
我利用了Hashmap来存储图的所有点,而图中所有的点(即person类)与其他点的关系则需要查询获得,我在Myperson类中建立了 getAcquaintance()和 getValue()以便直接查询目标点的联通点与边权值。同时,在Myperson类中,我采用Hashmap来存储与该点联通的点和边权值,其中id为所有Hashmap的key,这样在加删点或边时都可以通过put或remove对两个Hashmap分别操作,复杂度都是O(1)。这样的缺点是存储时过于繁琐,会导致代码量增大,代码风格分很难保证。
对于IsCircle函数关于联通分支的查询,我采用了并查集算法:
public int find(int x) {
if (x == maper.get(x)) {
return x;
} else {
maper.replace(x, find(maper.get(x)));
return maper.get(x);
}
}
通过递归与路径压缩的方式维护并查集,使查询两点联通性的复杂度为O(1),极大程度地节约了时间复杂度。
对于queryLeastConnection函数关于最小生成树的查询,我采用了Kruskal算法。因为该算法是以边为核心,涉及连通性的查询,所以可以直接套用并查集的模板。同时,对于该算法我采用封装建立新类的方式,这样既能体现面向对象思想,便于程序拓展,又可减少MyNetwork类码量。该方法复杂度为O(Elog(E))。
public Person find(Person person) {
if (person == personHashMap.get(person)) {
return person;
} else {
personHashMap.replace(person, find(personHashMap.get(person)));
return personHashMap.get(person);
}
}
public int queryLeastConnection(int id) throws PersonIdNotFoundException {
if (this.contains(id)) {
Kruskal kruskal = new Kruskal();
ArrayList<Person> personArrayList = new ArrayList<>();
for (Person person : people.values()) {
if (isCircle(id, person.getId())) {
personArrayList.add(person);
}
}
if (personArrayList.size() == 1) {
return 0;
}
return kruskal.work(personArrayList);
} else {
throw new MyPersonIdNotFoundException(id);
}
}
对于sendIndirectMessage函数关于最短路径的查询,采用了Dijkstra算法。通过图中点的遍历寻找每次最小权值的边,进行筛选。关于该算法,本人没有进行堆优化,导致程序在强测互测中出现TLE。
三.问题修复
- 在本单元第一次作业时,由于本人疏忽,部分异常类的计数器没有正常工作(居然过了中测),导致强测出现严重bug。最后一个异常类cnt没有自增。
- 在本单元第三次作业时,Dijkstra算法没有采用堆优化,强测一个点有TLE。
四.心得体会
本单元的学习比起第二单元的电梯专题,还是比较轻松的。在我看来,本单元更像是算法课,因为一些函数让我们必须去考虑时空复杂度,利用更合理、优化过的算法去实现。
同时,JML是一个规范性标准,对于标准的学习是十分有必要的。在今后的学习乃至工作过程中,熟悉标准可以让我们少走弯路,也让彼此的交流变得准确高效。
此外,JML可以让我对代码的逻辑有了新的理解角度。我认为,JML的编写逻辑并不是在于过程,而是在于起点与终点。就如同本次作业的代码,一个JML可以有多种多样的算法来实现。所以,本次作业让我在今后编写代码的过程中不止于关注过程,也要关注起终点。
五.Network扩展
假设出现了几种不同的Person
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
- Person:吃瓜群众,不发广告,不买东西,不卖东西
如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)
新建Advertiser、Producer、Customer类,继承Person。
新建Product类,包含Id,value属性。
新建Products,记录所有出现的产品。
方法:
- 查询某product的value -- getValue()
/*@ public normal_behavior @ requires containsProduct(id); @ ensures \result == getProduct(id).value; @ also @ public exceptional_behavior @ signals (ProductIdNotFoundException e) !containsProduct(id); @*/ public /*@ pure @*/ int getValue(int id) throws ProductIdNotFoundException;
- 增加一个product -- addProduct()
/* @ public normal_behavior @ requires !(\exists int i; 0 <= i && i < Products.length; Products[i] == product); @ assignable Products; @ ensures (\exists int i; 0 <= i && i < Products.length; Products[i] == product); @ ensures (\forall int i; 0 <= i && i < \old(Products.length); @ (\exists int j; 0 <= j && j < Products.length; Products[j] == (\old(Products[i])))); @ also @ public exceptional_behavior @ signals (EqualProductIdException e) (\exists int i; 0 <= i && i < Products.length; @ Products[i].equals(product)); @*/ public void addProduct(/*@ non_null @*/Product product) throws EqualProductIdException;
- 发送广告 -- sendAdvertisement()
/*@ public normal_behavior @ requires containsMessage(id); @ assignable Advertisements; @ assignable people[*].Advertisements; @ ensures !containsMessage(id) && Advertisements.length == \old(Advertisements.length) - 1 && @ (\forall int i; 0 <= i && i < \old(Advertisements.length) && \old(Advertisements[i].getId()) != id; @ (\exists int j; 0 <= j && j < Advertisements.length; Advertisements[j].equals(\old(Advertisements[i])))); @ ensures (\forall int i; 0 <= i && i < people.length; person[i].likeType(\old(((Advertisement)getMessage(id))).getType) ==> @ ((\forall int j; 0 <= j && j < \old(person[i].getMessages().size()); @ person[i].getMessages().get(j+1) == \old(person[i].getMessages().get(j))) && @ (person[i].getMessages().get(0).equals(\old(getMessage(id)))) && @ (person[i].getMessages().size() == \old(person[i].getMessages().size()) + 1))); @ also @ public exceptional_behavior @ signals (MessageIdNotFoundException e) !containsMessage(id); @*/ public /*@ pure @*/ void sendAdvertise(int id) throws MessageIdNotFoundException;