OO第三单元总结

Posted hhh_forever

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OO第三单元总结相关的知识,希望对你有一定的参考价值。

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

这一部分基本上都由JML语言给出了程序设计的规格,所以也没啥特别需要交代的。总体上来说,我在编写代码时大致分为这几个步骤:

1.先阅读官方包中所有接口和抽象异常类的定义,关注于整个程序的代码架构并从OO角度理解该类需要实现什么;

2.然后再是较仔细地阅读所有类的JML语言,了解不同类中应有什么对象,应有哪些方法,这些方法实现了什么功能;

3.理清程序的设计架构,该部分由UML自动生成;

4.编写代码时先从“最为基层”的类(如 Person, Message)开始实现,这些类侧重于方法调用时的嵌套;再实现较为复杂的类(如Network),而此部分则开始真正意义上实现方法的功能。在实现过程中,需要关注用何种容器与何种数据结构存储的问题。

二、测试策略与部分bug分析

虽然本单元课程组推荐我们使用Junit进行测试,本单元的训练题也是学习Junit进行单元测试,但在第一次作业使用Junit后,我觉得此方法并不好用,而且大部分的bug其实都是ctle,来源于算法性能问题,因此在第三次作业中我采用的是对拍测试。

除去性能导致的tle之外,三次作业里我出现了两类bug:第一类是Group没有注意到人数上限为1111,即使满了也给他加进去;而第二类则是群发红包时,没有注意到群发给单人的数量要均分即(money / people.size()),而是直接给每个人都发了money数量的红包。应该说这两个bug都是出于阅读JML不仔细导致的,不存在程序的设计架构问题。因此我认为此单元最合适的测试策略还是仔细阅读JML

三、容器选择和使用

HashMap:最开始我都是使用Arraylist容器来存放,但在第一次作业互测中就因ctle被hack了。通过上网查找HashMap的内部实现机制才发现:HashMap的查找时间复杂度为O(1),而Arraylist的查找为一个for循环,时间复杂度O(n)(我采用的是比较low的遍历查找,其实可以使用二分查找等来优化,降到O(logn),但既然java已经提供了更好的实现机制,何乐而不为呢),因此之后的容器若需要实现使用根据id来查找的功能,就使用HashMap;而不需要按id查找的就直接使用Arraylist容器。

TreeMap:由于我实现isCircle方法使用的是并查集,合并结点时需要比较两个结点对应通路总结点数的大小,因此我使用了能自动排序的TreeMap容器。

PriorityQueue:在求最短路径时我采用的是堆优化的Dijkstra算法,为实现堆优化,我采用了java内部提供的PriorityQueue容器来存取遍历的路径。

MyPerson

private HashMap<Integer, MyPerson> acquaintance = new HashMap<>(); //存放结识的人
private HashMap<Integer, Integer> value = new HashMap<>();      //存放结识的人对应的value值
private ArrayList<Message> messages = new ArrayList<>();    //存放收到的messages

 

MyGroup

private final ArrayList<Person> people = new ArrayList<>();

 

MyNetwork

    private final HashMap<Integer, MyPerson> people;
    private final HashMap<Integer, MyGroup> groups;
    private final HashMap<Integer, MyMessage> messages;
    private final ArrayList<Integer> emojiIdList;
    private final ArrayList<Integer> emojiHeatList;
    private final TreeMap<Integer, Integer> circle;         //用于并查集查找
    private final TreeMap<Integer, Integer> sum;            //用于并查集查找

 

四、性能分析

本单元出现性能问题都是因为算法的时间复杂度过高,凭经验来说一般时间复杂度在O(n^2)及以上就很可能会出现tle的问题。本单元最可能出现tle的方法有queryBlockSum, sendIndirecetMessage,getValueSum和getAgeVar。

queryBlockSum:最开始是简单暴躁的遍历实现,两个for循环,时间复杂度为O(n^2),所以第一次互测就被人刀傻了。针对第一次被hack的bug,我采用的是采用连通分量来边读入边存储更新结点的连通分量,而queryBlockSum则是在addPerson和addRealtion中直接进行更新计算。但遗憾的是,在第二次作业的强测中又出现了这个bug。在和同学学长交流之后,我决定采用祖传的并查集,最终成功AC了。

    public int find(int id) {
        if (circle.get(id) == id) {
            return id;
        } else {
            circle.put(id, find(circle.get(id)));
            return find(circle.get(id));
        }
    }
​
    public boolean isCircle(int id1, int id2) {
        return find(id1) == find(id2);
    }

 

sendIndirectMessage:这个方法中最容易超时的是求最短路径,若直接采用Dijkstra算法,时间复杂度将为O(n^2),由于数据最长可以达到10000行,故直接使用很可能会tle。我采用了堆优化,可以将时间复杂度降低到O(n*logn),极大地提高了性能。而且由于java内部已有优先队列PriorityQueue容器,故堆优化的实现将非常简单。只需要再新增一个Edge类,并实现compareTo方法。

public class Edge implements Comparable<Edge> {
    private int length;
    private Person start;
    private Person end;
​
    @Override
    public int compareTo(Edge o) {
        return Integer.compare(length, o.getLength());
    }
}

 

getAgeVar:与前两个不同,这个性能问题没有设计到算法问题,只是单纯的数学知识的优化问题,利用数学知识我们可得到简化的方差公式,只要定义一个sumAge和sumAge2分别用来表示年龄之和与年龄平方数之和,在addPerson和delPerson时注意维护一下即可。

getValueSum:这个方法我最初是两次for循环遍历,时间复杂度为O(n^2),在第三次作业中的强测中出现了一次tle,之后的优化我仅仅是将时间进行折半处理就AC了,但复杂度仍为O(n^2),依然有出现tle的隐患。但对此我也没能想到很好的方法降低其复杂度。

五、作业架构设计

本单元的架构设计已经由官方包给出,因此我们不许考虑太多,只需按照JML规格实现即可。除了官方包给出的类外,在第三次作业实现最短路径时我还新增了Edge类用来表示一条通路。

关于图模型的构建,JML规格大致已为我们实现,Network对应一张图,而Person对应图中的每一个结点,添加一个Person就是添加一个结点,而添加关系就是实现两结点间连线,value则表示这条边的权值。而MyPerson类中的acquaintance用于存放结识的人,在图中其实就是一个邻接表。除此之外,由于使用了并查集算法,故还构建了一个新的图,其结点与Network的结点完全相同。

以上是关于OO第三单元总结的主要内容,如果未能解决你的问题,请参考以下文章

oo第三单元总结

OO第三单元总结

OO第三单元总结

OO第三单元总结

OO第三单元总结

OO第三单元总结