BUAAOO第三单元总结
2021/6/1 10:21:30
本文主要是介绍BUAAOO第三单元总结,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
第三单元总结博客
设计策略
第九次作业实现的东西并不复杂,只是简单的提供了一个社交系统的必要组成部分,包括Person类和支持简单操作的Network类。考虑到person的id是唯一的,因此在本次架构中大多数的容器都采用了以id作为hash值索引的存储方式,主要应用的是HashTable<>()
后两次作业在架构上没什么可说的,仍然是增加了一些社交系统的组成部分,对于Message类和Group类我们很容易想到继续采用HashTable<>()进行存储,重点在于如何实现对应的功能和提高性能,这两部分在后面会阐述。
在本单元测试环节,我尝试了Junit方法测试规范性,但很快还是认为对拍方式和鲁棒性测试效率更高,后来的两次作业都采用了后者进行测试。
容器选择
- 由于Person、Message、EmojiMessage、Group的id都是唯一确定的,因此为了提高性能,有关上述对象的存储都使用HashTable容器
- 对于Person.messages,通过阅读JML规格看出sendMessage方法和getRecievedMessage方法是依赖于收发顺序的,因此采用了Stack作为容器进行存储
性能优化
第九次作业
本次作业中最大的优化点应该就是判断两点是否连通的isCircle方法和计算连通分支数的quaryBlockSum方法。考虑到本单元作业是一个动态变化的图结构,我选择了一个辅助类作为性能优化的工具,与堆优化类似,但可以随着作业迭代增加新的功能。
public class Blocks { private Hashtable<Integer, Integer> index = new Hashtable<>(); private Hashtable<Integer, LinkedList<Integer>> blocks = new Hashtable<>(); //blocks: <街区号, 街区<person.id>> //index:<person.id, 所在街区>,用于对id索引查表 //增加新的Person, 需要增加新的街区,同时在index表中增加新的key值,由于街区号其实意义并不太大,只要不重复就行,因此依然使用了person.id public void addPerson(int pid) { blocks.put(pid, new LinkedList<>()); blocks.get(pid).add(pid); index.put(pid, pid); } //核心方法之一,返回一个personId对应的街区号 public int pid2index(int pid) { return index.get(pid); } //当增加新的关系时,若两人不在同一街区,将街区合并,并更新索引表 public void addRelation(int id1, int id2) { if (pid2index(id1) == pid2index(id2)) { return; } else { int tmp = pid2index(id2); int des = pid2index(id1); for (int i : blocks.get(tmp)) { blocks.get(des).add(i); index.put(i, des); } blocks.remove(tmp); } } //返回街区总数 public int getSize() { return blocks.size(); } }
这是第九次和第十次作业使用的Block类,第十一次作业对其进行了重构。该类的核心功能是按照person1和person2之间是否认识划分街区,通过判断person1和person2是否在同一街区判断是否连通,通过街区总数计算BlockSum,具体方法含义见代码区。
第十次作业
主要性能损失在Group类的几个计算方法中,解决方法很简单,在addPerson的时候就进行运算,减少每次的遍历开销,这里用到了概率统计的公式,但是利用简单的因式分解也能写出来。
private int agesum = 0; private long agesquaresum = 0; private int valuesum = 0; @Override public void addPerson(Person person) { int age = person.getAge(); people.put(person.getId(), (MyPerson) person); agesum += age; agesquaresum += age * age; for (Integer i : people.keySet()) { if (people.get(i).isLinked(person)) { addValue(people.get(i).queryValue(person)); } } } @Override public void delPerson(Person person) { int age = person.getAge(); for (Integer i : people.keySet()) { if (people.get(i).isLinked(person)) { valuesum -= people.get(i).queryValue(person) * 2; } } people.remove(person.getId()); agesum -= age; agesquaresum -= age * age; } /* ... */ @Override public int getAgeMean() { int size = getSize(); if (size == 0) { return 0; } return agesum / size; } @Override public int getAgeVar() { int size = getSize(); if (size == 0) { return 0; } int ageMean = getAgeMean(); return (int) ((agesquaresum + size * ageMean * ageMean - 2 * ageMean * agesum) / size); }
第十一次作业
显而易见,主要的性能损失在求解最短加权路径上,这里考虑到图的结构经常变化,因此采用了迪杰斯特拉算法,因为其复杂度比较低。同时为了充分利用我们之前设立的Blocks类,显然在计算是否存在最短加权路径时,首先要考虑是否两个人在相同街区,然后对相同街区内的人进行遍历即可。为此的话对之前的Blocks类进行了修改。
public class Blocks { //构造了一个内部类,用于封装前两次作业中的单个街区 private class Block { private HashSet<Integer> pidlist = new HashSet<>();//街区中person的id集合 private Hashtable<Integer, Hashtable<Integer, Integer>> distancemap = new Hashtable<>();//街区中任意两点之间的最短路径 public void addpid(int pid) { pidlist.add(pid); } public HashSet<Integer> getPidlist() { return pidlist; } //查询是否存在id1到id2的最短路径的缓存,默认返回-1 public int qds(int id1, int id2) { if (!distancemap.containsKey(id1)) { return -1; } if (!distancemap.get(id1).containsKey(id2)) { return -1; } return distancemap.get(id1).get(id2); } //重置整个map,每当有新人加入街区时都重置该街区的map public void resetDistance() { if (!distancemap.isEmpty()) { distancemap.clear(); } } //写最短路径,path(id1->id2)=dis public void setDistance(int id1, int id2, int dis) { if (!distancemap.containsKey(id1)) { distancemap.put(id1, new Hashtable<>()); } distancemap.get(id1).put(id2, dis); } } private Hashtable<Integer, Integer> index = new Hashtable<>(); private Hashtable<Integer, Block> blocks = new Hashtable<>(); public void addPerson(int pid) { blocks.put(pid, new Block()); blocks.get(pid).addpid(pid); index.put(pid, pid); } public int pid2index(int pid) { return index.get(pid); } public void addRelation(int id1, int id2) { if (pid2index(id1) == pid2index(id2)) { blocks.get(pid2index(id1)).resetDistance(); return; } else { int tmp = pid2index(id2); int des = pid2index(id1); for (int i : blocks.get(tmp).getPidlist()) { blocks.get(des).addpid(i); index.put(i, des); } blocks.remove(tmp); blocks.get(des).resetDistance(); } } public boolean iscircle(int id1, int id2) { return pid2index(id1) == pid2index(id2); } public int getSize() { return blocks.size(); } //查询是否存在从id1到id2的最短路径缓存 public int qbs(int id1, int id2) { int index = pid2index(id1); return blocks.get(index).qds(id1, id2); } public void setBlockDistance(int id1, int id2, int distance) { blocks.get(pid2index(id1)).setDistance(id1, id2, distance); } //返回person.id对应的街区内的personList public HashSet<Integer> getBlock(int pid) { return blocks.get(pid2index(pid)).getPidlist(); } }
感想
相较于前两单元的作业来说,本次作业可以说是非常简单了,只要先宏观理解JML规格以及最终要实现的功能,然后根据经验选择合适的容器及算法就好,适当的使用缓存模式进行优化。但是要注意在细节上要符合JML规格,如涉及到计算的方法要注意JML的计算过程,防止出现误差;涉及到复杂逻辑的方法要注意JML的判断顺序,防止出现漏判误判。
对于异常类的处理实际比较简单,只需要按照要求在每个类内增加一个计数器就好。本次作业我没有单独增加计数器类,是因为觉得对于简单的异常类,直接复制粘贴建类也很方便,但是随着异常类的增多,管理起来也比较复杂,手动debug输出信息时也比较难找。在第二次作业中就出现了因为在第一次作业debug过程中留下的System.out语句没删,导致评测始终不通过的尴尬场景,这也提醒我们良好的架构不仅有利于读懂代码,还有利于后期的修改和维护,这在以后的代码协作过程中十分重要。
这篇关于BUAAOO第三单元总结的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-27OpenFeign服务间调用学习入门
- 2024-12-27OpenFeign服务间调用学习入门
- 2024-12-27OpenFeign学习入门:轻松掌握微服务通信
- 2024-12-27OpenFeign学习入门:轻松掌握微服务间的HTTP请求
- 2024-12-27JDK17新特性学习入门:简洁教程带你轻松上手
- 2024-12-27JMeter传递token学习入门教程
- 2024-12-27JMeter压测学习入门指南
- 2024-12-27JWT单点登录学习入门指南
- 2024-12-27JWT单点登录原理学习入门
- 2024-12-27JWT单点登录原理学习入门