ICode9

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

BUAAOO 第三单元总结

2021-05-31 02:02:47  阅读:222  来源: 互联网

标签:总结 non int 作业 private BUAAOO JML public 单元


 

BUAAOO 第三单元总结

 

一、实现规格所采取的设计策略

 

1.通读JML

 

三次作业下来,我都是采取先通读一遍JML规格,大致理解整体的架构,这么做的主要目的就是分析采取的数据结构,以免后面写着写着发现有更高效的数据结构。这里需要做的主要是分析JML规格中对数组的操作,比如第一次作业中Network中有根据id获得Person的操作,我最终就决定了采取Hashmap,又如

 

/*@ public instance model non_null Person[] acquaintance;
 @ public instance model non_null int[] value;
 @*/

 

通过后面的分析就可以知道这两个实际上是一一对应的,这样就完全没必要设置两个数组了,于是我就用了Hashmap

 

private final HashMap<Person, Integer> acquaintance;

 

这显然极大地方便了操作。第三次作业中的

 

/*@ public instance model non_null int[] emojiIdList;
 @ public instance model non_null int[] emojiHeatList;
 @*/

 

也是同理,用一个Hashmap就可以了。

 

我认为,JML只是告诉了我们一条绝对正确的道路(只要经过形式化验证),但是完全没有顾及到性能,所以我们照着JML写代码的时候,千万记得分析他的时间复杂度,而时间复杂度最容易出问题的地方就是数据结构,所以在我们动手写代码之前,要仔细分析项目中数据结构的作用,选择合适的容器,在满足规格描述的同时尽可能地降低时间复杂度。另外一点和前面地数据结构略有不同,就是看着很简单的代码,实际上因为反复的操作而增加运行时间,最明显的就是第二次作业中的getValueSumgetAgeMeangetAgeVar,这个时候就需要思考架构了,将指令实现的步骤分摊开来,减少重复操作即可。总而言之,刚开始需要通读JML了解整体架构,针对时间复杂度采取相应的架构。

 

2.代码实现

 

JML的第三单元代码明显就比前两个单元好些多了,因为根本不需要思考太多,整体的架构都已经搭好了,并不会出现边写边怀疑整体架构的问题,这就造成了这个单元第一次作业是最轻松的一次,而前两个可以说是最难的一次。

 

照着JML写代码无非就是读懂前置条件以及后置条件,然后相应地加上if-else即可。看JML最烦的一点就是得一条一条看过去,尤其是那些维护数组原先成员的规格描述,这是最浪费时间的,不过也是最能让我们感受到JML强大的地方。然后最困难的就是几个语句嵌套在一起,这使得我们必须断好句,否则可能出现理解的偏差,而理解的偏差是注定导致程序的错误的。

 

二、基于JML规格测试的方法和策略

 

这次的测试基本上只需要看看实现的函数是否严格按照JML的要求来写即可,所以我在测试的时候尽可能将JML所描述的几个分支都测了一遍,当然主要是看看边界条件。

 

符合JML的描述只是满足了程序的正确性,我将重心放在了后面测试性能的方面,JML的描述中高时间复杂度的地方我构造了一些测试数据,也就是卡TLE,比如第二次作业getValueSumgetAgeMeangetAgeVar就是我重点针对的内容,直接不加思考按照规格来写肯定不行,所以专门测试(主要是互测)这些地方就很有必要了,又比如最后一次作业的sendIndirectMessage显然是最容易测出代码时间复杂度的地方。

 

专门卡TLE的测试数据实际上就非常好构造了,只需要根据指令条数算出时间最大值,再用程序生成指令即可。

 

三、容器选择和使用的经验

 

第一次作业

 

// Person类
/*@ public instance model non_null Person[] acquaintance;
 @ public instance model non_null int[] value;
 @*/
private HashMap<Person, Integer> acquaintance; //<=我使用的,因为上述两个数组实际就是一一对应的
// Network类
/*@ public instance model non_null Person[] people;
 @*/
private HashMap<Integer, Person> personMap; //<=我使用的,便于利用id获得person

 

第二次作业

 

// Person类
/*@ public instance model non_null Message[] messages;
 @*/
private final ArrayList<Message> messages; //<=我使用的,因为考虑到后面的数组顺序问题
// Network类
/*@ public instance model non_null Group[] groups;
 @ public instance model non_null Message[] messages;
 @*/
private HashMap<Integer, Group> groupMap;
private HashMap<Integer, Message> messageMap; //<=我使用的,因为都和id有关,所以比较方便
// Group类
/*@ public instance model non_null Person[] people;
 @*/
private final HashMap<Integer, Person> personMap; //<=我使用的,和Network类中同理

 

第三次作业

 

// Network类
/*@ public instance model non_null int[] emojiIdList;
 @ public instance model non_null int[] emojiHeatList;
 @*/
private HashMap<Integer, Integer> emojiMap; //<=我使用的,也因为他们是一一对应的

 

显而易见,Hashmap是真的适合这个单元,当然可能还会有更好的容器,但我没有去深入了解,第一反应就是显然是用Hashmap啊,这也反映出Hashmap适用的场景,尤其是通过某个索引值(比如id)获得某个对象的时候。

 

当然,当我们需要得到一个有序的数组的时候,比如有很多的查询最值的需求的时候,我们可能就需要Treemap之类的了,这样能够提升程序的性能。

 

四、性能分析

 

这个单元我并没有在性能方面丢分(这里吐槽一点:第一次作业我因为部分异常处理的时候开头多加了个空格,直接给我强测30分,这种BUG中测测不出来不就是搞心态吗???去掉空格就A了,这是真的离谱,强烈要求这里有所改进!),下面根据三次作业逐一分析。

 

第一次作业

 

public boolean isCircle(int id1, int id2) throws PersonIdNotFoundException;
public int queryBlockSum();

 

毫无疑问这里是卡时间复杂度的地方,这里我使用了并查集

 

// Network类
private HashMap<Integer, Integer> fatherMap; //记录父亲(祖先)节点
private int fatherNum; //记录祖先的数量,即有几个连通块,在queryBlockSum直接输出即可
public int fd(int x) { //查询祖先节点,这样在isCircle中只需要比较两个节点的祖先节点是否相同即可
       if (fatherMap.get(x) == x) {
           return x;
      } else {
           int father = fd(fatherMap.get(x));
           fatherMap.replace(x, father);
           return father;
      }
  }

 

第二次作业

 

这次作业中最容易TLE的是getValueSumgetAgeMeangetAgeVar这三个函数,主要是每次查询都真的进行遍历会造成很多的重复操作,因此我设置了这三个变量用以保存需要的量

 

private int valueSum; //value和
private int ageSum; //age和
private int ageSquareSum; //age平方和

 

在查询指令来的时候只需要直接返回这些值,或者花一两步计算一下即可,这样就将查询时的遍历工作分配到了更新维护上面,然后我们只需要在会涉及更改这些量的地方进行更新就行,这里最需要注意的就是

 

public void delFromGroup(int id1, int id2)
throws GroupIdNotFoundException, PersonIdNotFoundException, EqualPersonIdException; //删人引起value值的变化

 

我是再进行了一次遍历,算出id1相关的value,实际上这是比较冒险的,可以从这里卡一波时间,但是按理论课老师讲的,实际程序中读操作会远多于写操作,所以这里还是无伤大雅的。

 

第三次作业

 

public int sendIndirectMessage(int id) throws MessageIdNotFoundException;

 

最短路径查询,这里我采用的是最常用的堆优化的dijistra算法,核心算法如下

 

public int djs(int id1, int id2) {
HashSet<Integer> visSet = new HashSet<>(); //标记访问过的节点
HashMap<Integer, Integer> disMap = new HashMap<>(); //记录节点的路径长度

Queue<Edge> queue = new PriorityQueue<>(); //堆优化
queue.add(new Edge(id1, 0));

disMap.put(id1, 0);
while (!queue.isEmpty()) {
Edge now = queue.poll();
int u = now.getTo();
int disToU;
disToU = disMap.getOrDefault(u, 2147483647);
if (disToU < now.getCost()) {
continue;
}
if (visSet.contains(u)) {
continue;
}
visSet.add(u);
Person personNow = getPerson(u);
for (Person person : ((MyPerson) personNow).getAcquaintance().keySet()) {
int next = person.getId();
int cost = personNow.queryValue(person);
int costByU = disMap.get(u) + cost;
int disToNext;
disToNext = disMap.getOrDefault(next, 2147483647);
if (!visSet.contains(next) && (costByU < disToNext)) {
disMap.put(next, costByU);
queue.add(new Edge(next, costByU));
}
}
}
return disMap.get(id2);
}

 

五、架构设计

 

第一次作业

 

第一次作业自己设计的主要就是并查集相关的架构

 

// Network类
private HashMap<Integer, Integer> fatherMap; //用于记录节点的父亲(祖先)节点
private int fatherNum; //用于记录连通块个数
// public void addPerson(Person person) throws EqualPersonIdException;
// public void addRelation(int id1, int id2, int value) throws PersonIdNotFoundException, EqualRelationException; 在这两个函数中对上述两个数据进行更新

 

第二次作业

 

第二次作业自己设计的主要是几个预存的量

 

private int valueSum; //value和
private int ageSum; //age和
private int ageSquareSum; //age平方和

 

然后就是在相关函数中对这些值的更新,这里就不赘述了

 

第三次作业

 

第三次作业自己设计的主要是最短路径算法,上文已经分析过了

 

实际正如第一部分所说,主要就是设计JML中时间复杂度高的地方,三次作业中我自己设计的地方都是为了解决JML不能解决的时间复杂度问题,所以开始时的通读一遍还是很有必要的。

 

 

 

 

 

标签:总结,non,int,作业,private,BUAAOO,JML,public,单元
来源: https://www.cnblogs.com/BUAA19373421/p/14829801.html

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

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

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

ICode9版权所有