第三单元总结
第三单元总结
1.自测过程中测试数据的准备
在本单元中,我主要分两个方面来准备测试数据,一是保证程序正确性的随机生成数据,二是测试程序性能的数据,首先与第二单元不同的是,这部分程序的正确性可以通过对拍来验证,我们要做的就是读懂JML了解数据的范围(边界),再写测试程序来随机生成数据,最后再与同学对拍即可。并且由于有JML的加持,静态对比也是一种发现程序bug的好方式。第二部分就是测试程序性能的数据,这部分尽量要人工构造,而不是随机生成,因为要反复去测那几条时间复杂度比较高的指令,并且生成的关系图也不能是随机的,要保证节点数与边数的比例恰当,接着读出程序开始与结束的时间,保证不超时即可。
2.架构设计、图模型构建和维护策略
2.1架构设计
架构设计按照课程组给出的接口来实现就可以了,不需要自己进行设计。独立于给定接口之外的有几个类,一是Edge
边类里面存储的是两个PersonId
和他们之间的价值,这个是后面用来维护图用的。并且为了减少行数,我将Kruskal和Dijkstra两个算法单独拿出来,成了两个类,将传入参数设置为他们的构造方法,输出的内容也设置为其中的方法,其余内置的步骤全部设置为private
,由此实现了封装。
2.2图模型构建
一个图基本由两部分组成,点集和边集,在这里我将JML中的Person[] people
作为点集,用自己构造的ArrayList<Edge> edges
作为边集,这样我们就将整个社交网络图建立起来。
2.3维护策略
读了JML语言后,用 ArrayList
存储所有数据是我的第一想法,但使用ArrayList
的存储效率与查找效率都很低,在第一次作业体现不出来,但在后两次作业中就会造成超时。这是选择HashMap
进行存储是一个不错的选择,用id
作为key
,可以很好的进行查找。所以为了维持基本功能,我们首先要维护的就是HashMap
。
接下来第二个要维护的就是我们并查集的father
数组,但由于是person
的id
与id
间的对应关系,所以我们仍然选择HashMap
来维护这种对应关系。
第三个要维护的就是Kruskal
算法中单调递增的边集ArrayList<Edge> edges
,我们只需要在每次加入关系后调用一次Collections.sort(edges)
即可保证边集是按照value单调递增的。
3.代码实现出现的性能问题和修复情况
3.1第一次作业
在第一次作业中,代码的性能问题主要来自于query_block_sum
和query_circle
这两条指令,用宽度\深度优先搜索的性能不够好,是十分危险的,最后换成了并查集。网上有很多教程,最终选择了路径压缩加上按秩合并的并查集。
首先要定义两个HashMap
来作为我们寻找父亲和存储秩的数组,建立id
与id
,id
与秩的对应关系。
private HashMap<Integer,Integer> fa;
private HashMap<Integer,Integer> rank;
定义查找函数(查找时进行路径压缩)
public int find(int x) {
if (x == fa.get(x)) {
return x;
}
else {
fa.put(x,find(fa.get(x)));
return find(fa.get(x));
}
}
在加入人时加入相关信息。
public void addPerson(Person person) throws MyEqualPersonIdException {
fa.put(person.getId(),person.getId());
rank.put(person.getId(),1);
}
加关系时进行按秩合并
public void addRelation(int id1, int id2, int value) throws
MyPersonIdNotFoundException, MyEqualRelationException {
int x = find(id1);
int y = find(id2);
if (rank.get(x) <= rank.get(y)) {
fa.put(x,y);
}
else {
fa.put(y,x);
}
if ((rank.get(x) == rank.get(y)) && (x != y)) {
rank.put(y,rank.get(y) + 1);
}
}
之后这两条指令便可以简化成为,因为并查集内都是连通的。
public boolean isCircle(int id1, int id2) throws MyPersonIdNotFoundException {
if (!contains(id1)) {
throw new MyPersonIdNotFoundException(id1);
}
if (contains(id1) && !contains(id2)) {
throw new MyPersonIdNotFoundException(id2);
}
if (id1 == id2) {
return true;
}
return (find(id1) == find(id2));
}
public int queryBlockSum() {
int sum = 0;
for (int id : fa.keySet()) {
if (fa.get(id) == id) {
sum = sum + 1;
}
}
return sum;
}
3.2第二次作业
第二次作业出现的性能问题来自于两条指令,query_group_value_sum
和query_least_connection
。
对于前者,通过用HashMap
存储数据再进行查询即可解决。对于后者,我们可以采用并查集优化的Kruskal
算法来获得最小生成树,由于并查集我们已经写过了,所以并不是很难完成,NetWork
里的大并查集用来判断某个人是否在连通分量中,Kruskal
算法中的小并查集用来判断是否成环,具体的算法这里就不再赘述了。
3.3第三次作业
第三次作业的性能问题主要来自send_indirect_message
指令,这条指令对应着最短距离算法。纯粹的Dijkstra
时间复杂度为$o(n^2)$
这是十分危险的,所以我们采用堆优化的Dijkstra
算法,将时间复杂度降至$o(nlogn)$。并且堆优化不需要我们自己写,只需借助PriorityQueue
即可完成堆优化。
4.Network扩展
假设出现了几种不同的Person
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
- Person:吃瓜群众,不发广告,不买东西,不卖东西
如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格。
首先要定义产品,产品需要有产品种类,产品价格,并且需要在NetWork
中维护一个产品的热度,像emoji
那样,在每次发送广告后会使产品热度增加。
产品属性
/*@ public instance model int id;
@ public instance model int price;
@ public instance model int type;
@*/
社交网络中维护的量
/*@ public instance model non_null Product[] products;
@ public instance model non_null Advertisement[] advertisements;
@ public instance model non_null int[] productTypeList;
@ public instance model non_null int[] productHeatList;
@*/
首先定义增加一个产品种类。
/*@ public normal_behavior
@ requires !(\exists int i; 0 <= i && i < productTypeList.length; productTypeList[i] == type);
@ assignable productTypeList, productHeatList;
@ ensures (\exists int i; 0 <= i && i < productTypeList.length; productTypeList[i] == type &&
productHeatList[i] == 0);
@ ensures productTypeList.length == \old(productTypeList.length) + 1 &&
@ productHeatList.length == \old(productHeatList.length) + 1;
@ ensures (\forall int i; 0 <= i && i < \old(productTypeList.length);
@ (\exists int j; 0 <= j && j < productTypeList.length; productTypeList[j] == \old(productTypeList[i]) &&
@ productHeatList[j] == \old(productHeatList[i])));
@ also
@ public exceptional_behavior
@ signals (EqualProductTypeException e) (\exists int i; 0 <= i && i < productTypeList.length;
@ productTypeList[i] == type);
@*/
public void storeProductType(int type) throws ProductTypeException;
接着定义增加一个产品
/*@ public normal_behavior
@ requires !(\exists int i; 0 <= i && i < products.length; products[i].equals(product)) &&
(containsProductType(product.getProductType()));
@ assignable products;
@ ensures products.length == \old(products.length) + 1;
@ ensures (\forall int i; 0 <= i && i < \old(products.length);
@ (\exists int j; 0 <= j && j < products.length; products[j] == (\old(products[i]))));
@ ensures (\exists int i; 0 <= i && i < products.length; products[i] == product);
@ also
@ public exceptional_behavior
@ signals (EqualProductIdException e) (\exists int i; 0 <= i && i < products.length;
@ products[i].equals(product));
@ also
@ public exceptional_behavior
@ signals (ProductTypeNotFoundException e) !(\exists int i; 0 <= i && i < products.length;
products[i].equals(product)) && !(containsProductType(product.getProductType()));
@*/
public void addProduct(Product product) throws EqualProductIdException, ProductTypeNotFoundException;
最后定义发送广告(让商品热度增加)
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < advertisements.length; advertisements[i].getId == id) &&
(containsProductType(advertisements[i].getType()));
@ assignable advertisements, productHeatList;
@ ensures (\exists int i; 0 <= i && i < productTypeList.length && productTypeList[i] ==
@ (\old(getAdvertisement(id))).getType();
@ productHeatList[i] == \old(productHeatList[i]) + 1);
@ ensures !containsAdvertisement(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]))));
@ also
@ public exceptional_behavior
@ signals (AdvertisementIdNotFoundException e) !(\exists int i; 0 <= i && i < advertisements.length;
@ advertisements[i].getId == id);
@ also
@ public exceptional_behavior
@ signals (ProductTypeNotFoundException e) (\exists int i; 0 <= i && i < advertisements.length;
advertisements[i].getId == id) && !(containsProductType(advertisements[i].getType()));
@*/
public void sendAdvertisement(int id) throws
AdvertisementIdNotFoundException, ProductTypeNotFoundException;
5.心得体会
- 本单元让我了解了契约式编程的思想,对日后的学习工作有很大的帮助,并且在自己实现的过程中体会到了实现JML的过程中,可以按照JML的说发来,但是性能不一定好,简单来说就是做出来非常容易但做好难。并且也体会到了JML语言的严谨性,可以将所有逻辑都概括全面。
- 本单元的难点主要在优化性能上,这也是在图论学习以来第一次尝试使用堆或者并查集来优化算法。让我复习了这部分有关知识,提高了算法能力。
- 同时实现JML的过程中也让我感到灵活变通是十分重要的,如果一味的按照JML语言来维护数据结构会造成严重的性能下降。