2022 OO 第三单元个人总结
一、课程内容
本次作业依据课程组提供的JML规格与类的接口,自己实现能达到要求的类。
1. 框架设计
2. 算法性能
本次作业的难点我认为有两方面,一个是对于JML规格的阅读,另一个就是对于算法的选择。
对于测试限制,我们要避免出现O(n2)的时间复杂度,尽可能都限制在O(nlogn)。
维护变量代替直接查询
对于一些变量,如ageSum
,valueSum
等变量,如果每次都查询都进行计算,那该请求的时间复杂度将会达到O(n2),导致TLE,所以我们选择使用在Group中创建一个valueSum
变量,每次addPerson
, deletePerson
, addRelation
时对其进行维护,以保证每次查询的时间复杂度为O(1)。
并查集
对于isCircle
算法,要求寻找两点是否连接,也就是两点间是否有通路。采用并查在每次addPerson
, addRelation
时进行维护,使该复杂度在维护时为O(n),查询时为O(1)。
堆优化的prim算法
对于sendIndirectMessage
方法,需要使用dijkstra
图算法,而普通的dijkstra
算法的时间复杂度为O(n2),采用堆优化的dijkstra算法可以将复杂度降为O(mlogn)。
容器选择
在本单元中,大量使用了HashMap
容器来储存信息,以保证快速搜索。
另外对于图的信息储存,使用三个ArrayList
来存储图的所有边。
3. 测试方法
由于指令数量、形式有限,所以本单元我首先采用形式化测试,对于每个指令的不同情况进行测试正确性,之后通过随机生成指令与同学对拍测试。
我出现的主要问题包括:
-
使用
dfs算法
实现isCircle
复杂度为O(n3),导致CTLE; -
对于
ageMean
等函数没有考虑到除数为了0的情况,归根结底时对于JML规格的阅读不熟悉。
二、拓展思考
假设出现了几种不同的Person
-
Advertiser:持续向外发送产品广告
-
Producer:产品生产商,通过Advertiser来销售产品
-
Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
-
Person:吃瓜群众,不发广告,不买东西,不卖东西
如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格
相关接口
可见,Advertiser
, Producer
, Customer
均继承自Person
类。
对于Customer
与Producer
之间的购买行为,Person
本身具有Money
属性,可以通过RedEnvelopeMessage
来实现。
对于Customer
与Advertiser
之间的传递产品信息的行为,以及Producer
与Customer
销售产品的行为,可以通过NoticeMessage
来实现。
注意,此时忽略了Customer
应支付给Advertiser
推销金钱,但也可通过RedEnvelopeMessage
来实现。
public interface Producer extends Person {
//让advertiserId推销noticeMessage
public void publishAdvertisement(int advertiserId, NoticeMessage noticeMessage) throws PersonIdNotFountException;
}
public interface Advertiser extends Person {
//推销noticeMessage,只发type == 1
public void advertise(NoticeMessage noticeMessage);
}
public interface Customer extends Person {
//发送redEnvelopeMessage
public void purchase(RedEnvelopeMessage redEnvelopeMessage);
}
JML规格
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < advertisers.length; advertisers[i].getId() == advertiserId)
@ assignable advertisers[*].messages
@ ensures (\forall int i; 0 <=i && i < advertisers.length && advertiserId != advertisers[i].getId();
@ advertisers[i].messages == \old(advertisers[i].messages));
@ ensures (\forall int i; 0 <=i && i < advertisers.length && advertiserId == advertisers[i].getId();
@ advertisers[i].messages.length == \old(advertisers[i].messages.length) + 1);
@ ensures (\forall int i; 0 <=i && i < advertisers.length && advertiserId == advertisers[i].getId();
@ advertisers[i].messages[0] == noticeMessage);
@ ensures (\forall int i; 0 <=i && i < advertisers.length && advertiserId == advertisers[i].getId();
@ \forall(int j; 0 <= j && j < \old(advertisers[i].messages.length);
@ advertisers[i].messages[j + 1] == \old(advertisers[i].messages)[j]));
@ public exceptional_behavior
@ signals (PersonIdNotFountException e) ! (\exists int i; 0 <= i && i < advertisers.length;
@ advertisers[i].getId() == advertiserId)
@*/
public void publishAdvertisement(int advertiserId, NoticeMessage noticeMessage) throws PersonIdNotFountException;
/*@ public normal_behavior
@ assignable noticeMessage.getGroup.people[*].messages
@ ensures (\forall int i; 0 <=i && i < noticeMessage.getGroup.people.length;
@ noticeMessage.getGroup.people[i].messages.length
@ == \old(noticeMessage.getGroup.people[i].messages.length) + 1);
@ ensures (\forall int i; 0 <=i && i < noticeMessage.getGroup.people.length;
@ noticeMessage.getGroup.people[i].messages[0] == noticeMessage);
@ ensures (\forall int i; 0 <=i && i < noticeMessage.getGroup.people.length
@ \forall(int j; 0 <= j && j < \old(noticeMessage.getGroup.people[i].messages.length);
@ noticeMessage.getGroup.people[i].messages[j + 1] ==
@ \old(noticeMessage.getGroup.people[i].messages)[j]));
@*/
public void advertise(NoticeMessage noticeMessage);
/*@ public normal_behavior
@ assignable producer[*].messages
@ ensures (\forall int i; 0 <=i && i < producer.length && redEnvelopeMessage.getPerson2.getId() !=
@ producer[i].getId(); producer[i].messages == \old(producer[i].messages));
@ ensures (\forall int i; 0 <=i && i < advertisers.length && redEnvelopeMessage.getPerson2.getId() ==
@ producer[i].getId(); producer[i].messages.length ==\old(producer[i].messages.length) + 1);
@ ensures (\forall int i; 0 <=i && i < producer.length && redEnvelopeMessage.getPerson2.getId() ==
@ producer[i].getId(); producer[i].messages[0] == redEnvelopeMessage);
@ ensures (\forall int i; 0 <=i && i < producer.length && redEnvelopeMessage.getPerson2.getId() ==
@ producer[i].getId(); \forall(int j; 0 <= j && j < \old(producer[i].messages.length);
@ producer[i].messages[j + 1] == \old(producer[i].messages)[j]));
@*/
public void purchase(RedEnvelopeMessage redEnvelopeMessage);
三、个人感悟
本单元的学习,从知识上让我学会了阅读JML规格,对于JML规格的实现有了基本的了解。对于JML规格的优缺点有了一定的思考,其在复杂函数的规格限制上具有简洁性,但对于另一些算法就会显得臃肿。
另外在算法层面,本次作业让我了解了许多减少复杂度的方法,对于维护变量来替代历次计算的思想,我认为时很重要的收获。