BUAA OO 2022 Unit3 总结
一、自测过程
在本单元的自测过程中,前两次的作业我主要通过JML规格的一些边界条件来自己构造测试数据,测试程序的正确性。比如第一次单元主要测试的是isCiercle函数的正确性,建立不同的person关系图验证正确性。第二单元则是对最小生成树的数据进行测试,采用了随机生成数据的办法来测试。但是这样一些没有注意到的边界条件就会忽略考虑而出现错误,并且生成数据的测试强度不够,对能达到的效率也会出现误判,比如第一单元我起初没有使用并查集而是使用了一定优化的遍历方案,到了强测的时候还是会有一个点超时错误。第三单元的测试则是借用了同学的测试程序对代码进行测试的。
二、图模型的构建和维护作业结构
本次的作业背景其实就是一个社交网络的模型,每个person数据就代表着一个个点,这些点的一些关系通过network进行维护,比如这些点通过addRealation的方法进行连通这就是模拟两个人互相认识了,通过isCiercle来确定两个点的连通关系即是否在一个生活圈子,通过message进行社交消息的传递,包括后两次作业的queryLeastConnection找最小生成树,和sendIndirectMessage求最短路径等都是一个社交网络模型的模拟。
采用的存储方式和算法
本次存储数组的结构我大都采用ArrayList的结构进行存储,好处就是这是不定长的,可以随时增添删减,并且可以通过下标来遍历或者查找,并且类似与emoji的Id和Heat的顺序对应关系可以直接通过下标对应起来。
第一次作业的难点在isCircle方法。为了对付cpu超时的问题,我采用的是并查集的方法。对于并查集最重要的就是对顶级父节点的查找和merge的合并树
查找顶级父节点的代码如下
public int find(int id) { if(getPerson(id).getFatherId()==id) return id; else return find(getPerson(id).getFatherId()); }
另一部分合并树是在每次进行addRelation添加边时,便进行合并树的操作,即查询添加关系的两个person如果已经在一颗树上则不操作,否则进行小树合并到大树的操作,这样时间复杂度便是O(Elog(E)),部分代码如下
id1Root=find(id1); id2Root=find(id2); if(id1Root!=id2Root) { if(getPerson(id1Root).getDepth()<getPerson(id2Root).getDepth()) { getPerson(id1Root).setFatherId(id2Root); } else { getPerson(find(id2)).setFatherId(id1Root); if(getPerson(id1Root).getDepth()==getPerson(id2Root).getDepth()) getPerson(id1Root).setDepth(); } }
第二次作业的作业难点在于实现最小生成树,这里我采用的是Prim算法,并且采用了JAVA的PriorityQueue这种优先队列的存储方式,来实现最小堆优化的Prim算法方式实现。这种算法关注的是点,以一个点初始,每次遍历所有的点中与这个初始点的距离,并将最小的点加入集合,之后更新距离知道所有点都已加入集合为止。一部分实现代码如下
q.add(new Nodeing(0,0)); while(!q.isEmpty()) { Nodeing node=q.poll(); if(vis[node.side]) continue; vis[node.side]=true; sum=sum+node.val; for(int i=1;i<allNode.size();i++) { if(this.getPerson(allNode.get(node.side)).isLinked(this.getPerson(allNode.get(i)))&& this.getPerson(allNode.get(node.side)).queryValue(this.getPerson(allNode.get(i)))<leastcost[i]) { leastcost[i]=this.getPerson(allNode.get(node.side)).queryValue(this.getPerson(allNode.get(i))); q.add(new Nodeing(i,leastcost[i])); } } }
第三次作业的难点在于实现最短路径算法,我采用的同样的用PriorityQueue这种优先队列来存储,形成最小堆,用dijkstra算法来实现最短路径算法。这种算法的想法是维护所有点到初始点的距离,每次选择到初始点距离最短的点加入为已知路径的集合,并在每次加入一个点到已知最短路径的集合要更新其他点的距离,最后一直到目标点加入已知最短路径的集合。
三、BUG及修复第九次作业的bug主要是cpu运行时间超时的问题,具体原因是使用的还是DFS的遍历方式去完成isCiercle这个方法而没有采用并查集去完成,还有一个是JML的规格括号搞错了导致isLinked这个方法判断条件出错。
第十次作业的强测还是因为cpu超时的问题,在计算最小生成树时没有采用堆优化,导致了超时,并且倒霉催的是修复完bug才发现bug修复时间搞错,前一天是deadline,没有提交上去bug修复。。
四、
四、Network 扩展
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
- Person:吃瓜群众,不发广告,不买东西,不卖东西
新建Advertiser、Producer、Customer类,继承Person
发送广告信息
/*@ public normal_behavior @ requires containsMessage(id)&&getMessage(id).getType() == 3; @ assignable messages; @ assignable getMessage(id).getPerson().messages; @ ensures !containsMessage(id) && messages.length == \old(messages.length) - 1 && @ (\forall int i; 0 <= i && i < \old(messages.length) && \old(messages[i].getId()) != id; @ (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i])))); @ ensures (\forall int i; 0 <= i && i < people.length; person[i].likeType(\old(getMessage(id)).getType) ==> @ ((\forall int j; 0 <= j && j < \old(person[i].getMessages().size()); @ person[i].getMessages().get(j+1) == \old(person[i].getMessages().get(j))) && @ (person[i].getMessages().get(0).equals(\old(getMessage(id)))) && @ (person[i].getMessages().size() == \old(person[i].getMessages().size()) + 1))); @ also @ public exceptional_behavior @ signals (MessageIdNotFoundException e) !containsMessage(id); @*/ public /*@ pure @*/ void sendAdvertiseMessage(int id) throws MessageIdNotFoundException;
购买商品
/*@ public normal_behavior @ requires contains(personId); @ requires containsProduct(productId); @ assignable getPerson(personId).money, getProduct(productId).numb @ ensures getPerson(personId).money = \old(getPerson(personId).money) - getProduct(productId).getValue; @ ensures getProduct(productId).getNumb = \old(getProduct(productId).getNumb) - 1; @ also @ public exceptional_behavior @ signals (PeronIdNotFoundException) !contains(personId); @ also @ public exceptional_behavior @ signals (ProductIdNotFoundException) !containsProduct(productId); @*/ public void purchaseProduct(int personId, int productId) throws PersonIdNotFoundException, ProductIdNotFoundException;
查询销量
/* @ public normal_behavior @ requires (\exists int i; 0 <= i && i < people.length; people[i].getId() == id && people[i] instanceof Producer); @ requires containsProduct(productId); @ \results == getPerson(id).getProduct(productId).money * (\sum int i; 0 <= i && i < people.length; (\exists int j; 0 <= j && j < people[i].products.length; people[i].products[j].equals(getPerson(id).getProduct(productId)))); @ public exceptional_behavior @ signals (PeronIdNotFoundException) !contains(personId); @ also @ public exceptional_behavior @ signals (ProductIdNotFoundException) !containsProduct(productId); */ public /*@ pure @*/ int querySalaryValue(int id, int productId) throws PersonIdNotFoundException, ProductIdNotFoundException;
五、学习体会
本单元主要学习了JML的规格化语言,这种语言主要包括前置条件,后置条件和副作用三个方面对方法进行描述,并且设立了不变式等必须时刻遵守的约束,通过这种语言可以更严格的给开发代码的人员进行要求,通过规划化语言的约束,不变式等的约束,可以让开发出来的代码更严谨,符合需求,这对于我们未来开发应用和他人的对接是非常有帮助的。
另外就我个人觉得这单元除了规格化语言的学习,更是一次算法的学习,学习了一些算法效率更高,时间复杂度更低的时间方式。