ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

BUAA OO 第三单元总结

2022-06-04 23:00:13  阅读:135  来源: 互联网

标签:OO throws producer 查集 BUAA 查询 JML 节点 单元


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

  • Advertiser:持续向外发送产品广告

  • Producer:产品生产商,通过Advertiser来销售产品

  • Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息

  • 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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有