其他分享
首页 > 其他分享> > BUAA OO 第三单元总结

BUAA OO 第三单元总结

作者:互联网

BUAA OO 第三单元总结

如何利用JML规格来准备测试数据

如果要"利用JML规格来准备测试数据",我认为最重要的就是能够测试到方法的每一种行为

avatar

上图中红线标注的部分即为每一种行为的前置条件。在对方法进行单元测试时,可以分别构造满足每一种前置条件的数据,观察方法能否正确地返回或抛出异常

架构设计

总括

第一单元给我的整体印象是"面向对象",第二单元给我的整体印象则是"多线程"

本单元给我的整体印象本应是"JML规格",可不知为何,每当提起本单元时,我总是会第一个想到"数据结构"

由于课程组已经完全确定了本次作业的整体继承结构,我们的工作重心就从"架构设计"转移到了"功能实现"上。而按照JML实现功能的同时,为了不超时,我们必须花费更多的时间去维护数据结构,改进算法。这或许就是造成上述现象的原因

具体内容

第11次作业为止,项目的主要架构如下

avatar

Network总揽全局,持有一些Person、Group、Message对象,并对其进行添加、删除、修改、访问操作

图模型构建及维护策略

总括

在Network操作这些对象的过程中,Person的isLinked方法和queryValue方法将分布在网络各处的人们连接起来,形成了一个带权无向图。于是乎,我们得以在网络中进行图论的一些经典操作,如"求两点是否连通"、"求最小生成树"、"求最短路径"等

queryBlockSum

这是一个值得一提的方法;虽然只有短短五行的JML规格,却很容易导致超时。我第九次作业强测时就在这里挂了两个点

如果完全按照JML规格的描述来实现此方法,时间复杂度会达到平方级别。但只要理解了blockSum的意义,就能写出优秀的实现

它的意义可以描述为:如果某个人和其它人都没有直接联系,blockSum就加一。于是我们只需维护一个blcokSum变量并将其初始化为零,在addPerson时将其加一,在对两个完全无关的人(即isCircle为假)addRelation时将其减一。查询时,直接将其返回即可

isCircle

isCircle即为求两点间连通性的方法,常用实现途径大概有:广度优先搜索、深度优先搜索、并查集

我最初使用的是广度优先搜索,虽然强测没有在这里出什么问题,互测时却被针对性hack了。当查询操作达到一定数量时,由于搜索算法的高时间复杂度,程序容易超时。当然,这时可以选择将搜索结果记录下来,以供后续使用。不过,如果在查询操作之间时不时地增添一条addRelation指令,这种方法就会失效。最可靠的方法是降低查询的时间复杂度,也就是使用并查集

使用并查集时,只需在addRelation内稍加合并,便可将查询的复杂度降到O(1)

并查集仍有优化的空间;可以为并查集的节点维护一个"深度",在合并时,总是选择深度更小的节点作为被合并对象;还可以使用"路径压缩",在查询根节点时,顺便将沿途节点全部连接到根节点上。这两种方法都可以尽量减少查询根节点时访问父节点的次数。两种方法还能混用,虽然此举会让节点的"深度"从"准确深度"变为"最大深度",但仍能进一步提升效率

queryLeastConnection

queryLeastConnection即为求最小生成树的方法,常用实现途径:Prim、Kruskal

我本来使用的是Prim(而且是堆优化后的Prim),奈何在某条数据下跑了三十多秒,被五秒的Kruskal完败,只好改为Krusal。听闻两者时间复杂度不相上下,不知为何,竟有如此差距。或许是因为Kruskal从最小的边开始搜索,能更快找出所有边吧

并查集的优势在这里再次显现。Prim也好,Kruskal也罢,如果每次queryLeastConnection时,都要搜索一次的话,程序很容易超时。要是可以用某种方式将之前的搜索结果记录下来,就再好不过了。而上次作业维护的并查集正好可以胜任这一工作;对于同一个连通块里的所有节点,它们的最小生成树权值是相同的,所以我们可以用根节点来记录这个数值。首先把所有节点的leastConnection属性都设为-1,表示未查询;在查询后更新leastConnection属性;在addRelation时,再次将根节点的leastConnection属性设为-1。这样一来,搜索次数就大大减少了

顺带一提,我本来的策略是:在每次addRelation时直接计算更新后连通块的leastConnection。这导致了许多无用搜索结果的产生,造成了时间浪费,还使我强测挂了一个测试点。多亏pxt的提醒,我才修复了这个bug

由于Kruskal可以用并查集优化,我又维护了一个并查集

sendIndirectMessage

sendIndirectMessage即为求最短路径的方法,没有负权边时,一般用Dijkstra来实现

Dijkstra和Prim的思路如出一辙,甚至还都能堆优化。我使用的就是堆优化的Dijkstra。我没有对查询结果做记录,以便下次查询,因为不像最小生成树,最短路径描述的就是两个单独的点之间的关系,需要使用一个二维数组才能完全记录结果,所占空间很大。而且就算把结果记录下来,随便一条addRelation就能破坏整个连通块内的最短路径关系,需要遍历二维数组才能使这些最短路径失效,可能会花费更多时间,得不偿失。

可惜,我在实现Dijkstra算法的过程中,寻找相邻点时,将所有的点都遍历了一遍,而不是去访问一个现成的acquaintance数组,浪费了时间,挂了一个强测点并在互测时被hack了四次

代码实现出现的性能问题和修复情况

已在上文详述,此处不再赘述

Network扩展

题目

假设出现了几种不同的Person

如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等
请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)

相关接口方法

//添加销售员
void addAdvertiser(Advertiser advertiser) throws EqualPersonIdException;

//添加生产商
void addProducer(Producer producer) throws EqualPersonIdException;

//添加消费者
void addCustomer(Customer customer) throws EqualPersonIdException;

//生产商派遣销售员
void dispatchAdvertiser(Producer producer,Advertiser advertiser) throws PersonIdNotFoundException, EqualPersonIdException;

//生产商取消派遣销售员
void cancelAdvertiser(Producer producer,Advertiser advertiser) throws PersonIdNotFoundException;

//顾客向销售员发送购买消息
void purchase(Customer customer,Advertiser advertiser) throws PersonIdNotFoundException;

//查询销售额
int querySalesVolume(Producer producer) throws PersonIdNotFoundException;

//查询销售路径
List<Advertiser> querySalesPath(Producer producer) throws PersonIdNotFoundException;

核心业务功能

//生产商派遣销售员
/*@ public normal_behavior
  @ requires contains(producer.getId()) && contains(advertiser.getId()) && 
  @          !producer.containsAdvertiser(advertiser.getId());
  @ assignable advertisers, producer.advertisers, advertiser.product;
  @ ensures producer.containsAdvertiser(advertiser.getId()) && 
  @         producer.advertisers.length == \old(producer.advertisers.length) + 1 &&
  @         (\forall int i; 0 <= i && i < \old(producer.advertisers.length) 
  @         (\exists int j; 0 <= j && j < producer.advertisers.length; 
  @         producer.advertisers[j].equals(\old(producer.advertisers[i]))));
  @ ensures !contains(advertiser.getId()) && advertisers.length == \old(advertisers.length) - 1 &&
  @         (\forall int i; 0 <= i && i < \old(advertisers.length) && 
  @         \old(advertisers[i].getId()) != advertiser.getId();
  @         (\exists int j; 0 <= j && j < advertisers.length; advertisers[j].equals(\old(advertisers[i]))));
  @ ensures advertiser.getProduct() == producer.getProduct();
  @ also
  @ public exceptional_behavior
  @ signals (PersonIdNotFoundException e) !contains(producer.getId()) || 
  @          !contains(advertiser.getId());
  @ signals (EqualPersonIdException e) contains(producer.getId()) && 
  @          contains(advertiser.getId()) && producer.containsAdvertiser(advertiser.getId());
  @*/
void dispatchAdvertiser(Producer producer,Advertiser advertiser) throws PersonIdNotFoundException, EqualPersonIdException;

//顾客向销售员发送购买消息
/*@ public normal_behavior
  @ requires contains(customer.getId()) && contains(advertiser.getId());
  @ assignable customer.products, customer.money;
  @ ensures customer.containsProduct(advertiser.getProduct()) && 
  @         customer.products.length == \old(customer.products.length) + 1 &&
  @         (\forall int i; 0 <= i && i < \old(customer.products.length) 
  @         (\exists int j; 0 <= j && j < customer.products.length; 
  @         customer.products[j].equals(\old(customer.products[i]))));
  @ ensures customer.getMoney() == \old(customer.getMoney()) - advertiser.getProduct().getPrice();
  @ also
  @ public exceptional_behavior
  @ signals (PersonIdNotFoundException e) !contains(customer.getId()) || 
  @          !contains(advertiser.getId());
  @*/
void purchase(Customer customer,Advertiser advertiser) throws PersonIdNotFoundException;

//查询销售路径
/*@ public normal_behavior
  @ requires contains(producer.getId());
  @ ensures  (\forall int i; 0 <= i && i < \old(customer.products.length);
             (\result).containsAll(producer.advertisers)) && 
             (\result).length == producer.advertisers.length;
  @ also
  @ public exceptional_behavior
  @ signals (PersonIdNotFoundException e) !contains(producer.getId());
  @*/
List<Advertiser> /*@ pure @*/ querySalesPath(Producer producer) throws PersonIdNotFoundException;

心得体会

JML的强大 —— 依靠强大的JML规格,我们得以将整体架构的设计交给同事(此处为课程组),自己专注于功能的实现,从而减轻许多心理上,甚至生理上的负担

功能实现的灵活 —— JML规格可以限制我们实现的功能,却无法限制我们功能的实现。同一种功能,不同的实现方式所花费的时间、空间可能是天壤之别

合作的愉悦 —— 团队合作能够提高完成任务的效率。找人对拍,分(bai)享(piao)数据,多是一件美事啊。特此感谢xrb,pxt,zyc,zwh的慷慨相助

标签:OO,throws,producer,查集,BUAA,查询,JML,节点,单元
来源: https://www.cnblogs.com/mayou-syq/p/16343017.html