OO第三单元总结
一、自测过程
这一单元的自测主要是依靠同学写的评测机,同时自己会构造一些边界数据进行检测。由于整个架构以及每个函数的作用都已经给了相应的规格,我们只需用性能较好的算法实现那些函数即可。
(1)第一次作业:这次作业涉及到了计算图的连通分量问题,在构造大数据时就会刻意设计连通分量较多的数据。还有一个就是异常类的判断和统计,还有多个异常的优先级,这个异常类机制实现不难,只要正确理解JML规格就行。
(2)第二次作业和第三次作业:这两次作业大同小异,这个代码的架构基本沿袭了第一次作业,只不过分别引入了最小生成树问题和最短路径问题,都是属于有板子的经典问题,在构造数据上会加入大量的高复杂度的指令,如qlc、sim等。
总的来说,这次作业的正确性一般不会出错,容易掉坑的是算法超时问题(课程组给的时间限制还挺短的),所以敲代码的时候会格外关注算法的复杂度。
二、图模型的构建和维护
第一次作业:
首先在第一次作业中JML规格用的是列表,我的容器就选择了ArrayList(本来以为之后会对容器内元素的顺序有要求,就没用HashMap,事实证明我想多了...)。不过好在这次作业ArrayList也能满足时间限制,侥幸逃过一劫。
在异常类的统计上采用静态变量进行统计,同时异常类的先后判断严格按照JML中的优先级。
在判断两点是否连通以及图的连通分量数量的问题上,我采用了并查集的思想。在NetWork中设置并查集,引入祖先节点和子节点的概念,若两个点之间存在路径则说明两点的祖先节点相同,而给两个点加连线只需让其中一个点的祖先节点是另一个点的祖先节点即可。
为了减少并查集的深度,在添加relation时尽量是整个并查集的深度最小,同时在查询过程中压缩路径,令祖父节点直接变成父节点。
代码如下:
public Leaf find(int x) { for (Leaf leaf : leaves) { if (x == leaf.getOwn()) { if (leaf.getOwn() == leaf.getFather()) { return leaf; } else { leaf.setFather(find(leaf.getFather()).getOwn()); return find(leaf.getFather()); } } } return null; } public void merge(int i, int j) { Leaf leaf1 = find(i); Leaf leaf2 = find(j); if (leaf1.getRank() <= leaf2.getRank()) { leaf1.setFather(leaf2.getOwn()); } else { leaf2.setFather(leaf1.getOwn()); } if (leaf1.getRank() == leaf2.getRank() && leaf1.getOwn() != leaf2.getOwn()) { leaf2.addRank(1); } }
第二次作业:
这一次作业考察最小生成树问题。说实话,我看JML规格看了快一个点才看出它要我算最小生成树。虽然JML语言很严谨,但相应地也放弃了一些可读性。最小生成树算是经典问题了,我采用Prim算法,算法的具体实现细节就不谈了。
因为本次作业指令最大数达到一万,用ArrayList显然是满足不了时间要求了,于是我把所有的ArrayList都改成了HashMap,而本次作业由于ID具备唯一性,也给了HashMap天然的优势。
这次作业还有个坑点就是qgvs指令,若是每次进行查询就从头算一遍group中的valueSum,时间复杂度显然超过了O(n2),因此可以设置一个统计量维护group的valueSum值,在group内元素发生变化时相应改变维护量即可,这样查询指令的复杂度就是O(1)。
第三次作业:
这一次作业考察最短路径问题,我采用的是堆优化的Dijkstra算法。用Java中的Queue容器来进行存储,每次寻找最短的一个relation是只需要调用poll方法即可。具体实现代码如下:
for (int i = 0; i < doneSpot.size(); i++) { int dex; Count count; while (true) { count = counts.poll(); if (doneSpot.get(count.getId()) == 0) { dex = count.getId(); break; } } doneSpot.put(dex, 1); if (dex == person2.getId()) { break; } MyPerson person = (MyPerson) people.get(dex); for (Integer num : person.getAcquaintance().keySet()) { int k = leastPath.get(dex) + person.queryValue(people.get(num)); if (leastPath.get(num) > k) { leastPath.put(num, k); Count count1 = new Count(num, k); counts.add(count1); } } }
同时这一次作业和上一次作业中都引入了message,在写代码的时候需要细心理解JML规格,分清楚每种message以及每一个函数的行为即可。
三、代码性能和Bug修复
本次作业如果JML规格理解正确,代码正确性基本不会有问题,bug一般都是超时问题。而我本单元的bug是出在第九次作业的查询连通分量个数,当时就直接翻译JML规格,通过调用isCircle方法计算连通分量个数,而不是用并查集的思想,导致方法复杂度直接爆炸,在互测中被干烂了。
在之后我就格外注意每个方法的复杂度,若没有指令数目的严格限制,则不能超过O(n2),在后两次作业中强测和互测都没有发现问题。
四、NetWork扩展
具体拓展代码如下:
public interface NetWork1 { /*@ public instance model non_null Advertisement[] Advertisements; @ public instance model non_null Product[] products; @ public instance model non_null Advertiser[] Advertisers; @ public instance model non_null Producer[] Producers; @ public instance model non_null Customer[] Customers; @*/ /*@ public normal_behavior @ requires (\exists int i; 0 <= i && i < advertisers.length; advertisers[i].equals(advertiser)) && @ (\exists int i; 0 <= i && i < products.length; products[i].equals(product)); @ assignable advertisers,products; @ ensures advertisements.length == \old(advertisements.length) + 1; @ ensures (\forall int i; 0 <= i && i < \old(advertisements.length); @ (\exists int j; 0 <= j && j < advertisements.length; advertisements[j] == (\old(advertisements[i])))); @ ensures (\exists int i; 0 <= i && i < advertisements.length; advertisement[i].getAdvertiser() == advertiser && @ advertisement[i].getProduct() == product); @ also @ public exceptional_behavior @ signals (AdvertiserNotFoundException e) !(\exists int i; 0 <= i && i < advertisers.length; @ advertisers[i].equals(advertiser)); @ signals (ProductNotFoundException e) (\exists int i; 0 <= i && i < advertisers.length;advertisers[i].equals(advertiser)); @ !(\exists int i; 0 <= i && i < products.length;products[i].equals(products)); @*/ public void sendAdvertisements(Advertiser advertiser, Product product) throws AdvertiserNotFoundException, ProductNotFoundException; /*@ public normal_behavior @ requires (\exists int i; 0 <= i && i < advertisers.length; advertisers[i].equals(advertiser)) && @ (\exists int i; 0 <= i && i < producers.length; produces[i].equals(producer)) ; @ assignable advertisers,producers; @ 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 < advertisers.length; advertisers[i].getInformation() == productInformation && @ advertisers[i].getAdvertiser == advertiser && advertisers[i].getProducer == producer); @ also @ public exceptional_behavior @ signals (AdvertiserNotFoundException e) !(\exists int i; 0 <= i && i < advertisers.length; @ advertisers[i].equals(advertiser)); @ signals (ProducerNotFoundException e) (\exists int i; 0 <= i && i < producers.length; @ producers[i].equals(producer)) && @ !(\exists int i; 0 <= i && i < producers.length; @ producers[i].equals(producers)) @*/ public void saleProduct(Advertiser advertiser, String productInformation, Producer producer) throws AdvertiserNotFoundException, ProducerNotFoundException; /*@ public normal_behavior @ requires (\exists int i; 0 <= i && i < advertisers.length; advertisers[i].equals(advertiser)) && @ (\exists int i; 0 <= i && i < customers.length; customers[i].equals(customer)) @ (\exists int i; 0 <= i && i < products.length; products[i].equals(producer)); @ assignable advertisers,products,customers; @ ensures advertiser.getMessage.length == \old(advertiser.getMessage.length) + 1; @ ensures (\exists int i; 0 <= i && i < advertiser.getMessage.length; advertiser.getMessage.get(i).getCustomer() == customer && @ advertiser.getMessage.get(i).getAdvertiser() == advertiser && @ advertiser.getMessage.get(i).getProduct() == product); @ also @ public exceptional_behavior @ signals (AdvertiserNotFoundException e) !(\exists int i; 0 <= i && i < advertisers.length; @ advertisers[i].equals(advertiser)); @ signals (ProducerNotFoundException e) (\exists int i; 0 <= i && i < producers.length; @ producers[i].equals(producer)) && @ !(\exists int i; 0 <= i && i < producers.length; @ producers[i].equals(producers)) @ signals (ProducerNotFoundException e) (\exists int i; 0 <= i && i < producers.length; @ producers[i].equals(producer)) && @ (\exists int i; 0 <= i && i < producers.length; @ producers[i].equals(producers)) && @ (\exists int i; 0 <= i && i < producers.length; @ producers[i].equals(producers)); @*/ public void sendSaleMessage(Customer customer,Advertiser advertiser,Product product) throws AdvertiserNotFoundException, CustomerNotFoundException,ProductNotFoundException; public void addPerson(Person person); public void addProducer(Producer producer); public void addAdvertiser(Advertiser advertiser); public void addCustomer(Customer customer); public void removePerson(Person person); public void removeProducer(Producer producer); public void removeAdvertiser(Advertiser advertiser); public void removeCustomer(Customer customer); }
其中sendAdvertisements是以advertiser为主体,根据product,来制作广告;saleProduct是以producer为主体,根据产品信息制作product,并投掷给相应的advertiser;而sendSaleMessage是以customer为主体,根据产品向相应的advertiser发送message。
五、学习体会
总的来说,本单元相较于前两个单元难度较低,毕竟不需要太多的设计,只需要读懂JML规格进行翻译即可。同时在学习的过程中,我也明白了JML语言的严谨和准确,虽然理解和编写它很困难,但在写JML的过程中,我也懂得了如何把握和提取方法的关键信息,怎么去寻找和编写前置和后置条件等,收获颇丰。