BUAA_OO第三单元总结

BUAA_OO第三单元总结

设计策略

  • 阅读接口的规格,理解程序需求

    • 要看清public normal_behavior和public exceptional_behavior的条件。
    • 对于规格存在'==>'的接口可以优先判断抛出异常再进行正常处理。
  • 基于理解选择合适的容器

    • 视情况选择合适的容器,如\(Treemap(Hashmap)\)\(HashSet\)\(LinkedList\)等。
  • 选择合适的算法避免TLE

    • 这单元中比较坑的点我觉得是第一次作业中的\(isCircle(int id1, int id2)\)\(queryBlockSum()\),此时可以通过并查集实现查询是否在于同一个连通块的操作,除此之外还有堆优化的迪杰斯特拉最短路径。

测试方法和策略

  • 对于功能较为复杂(特判条件较多)和时间复杂度较大的接口进行测试。
    • 因为规格存在'==>'的接口我都是优先判断抛出异常再进行正常处理的,所以我基本都会测试一遍检查方法的逻辑。
    • 对于时间复杂度较大的接口(例如\(queryBlockSum()\)\(getValueSum()\)\(sendIndirectMessage(int id)\)),可以针对此方法编写数据进行提前的hack测试。
  • 对拍测试
    • 通过和室友的对拍找bug。(第二次作业中\(getAgeVar()\)中没有除长度)

容器选择和使用经验

本单元中出现了许多id与对象对应的类,例如\(Person\)类、\(Group\)类、\(Message\)类和异常类。这些都是类似于拥有独一无二的id的类,所以在\(Network\)类中构建\(Person[ ] people\)\(Group[ ] groups\)\(Message[] messages\)等时,我都选择了Map一类(我个人更偏好 ,所以基本使用了\(Treemap\))。

在第三次作业中,迪杰斯特拉算法中存在查询节点是否被访问过的操作,我在这里使用了\(HashSet\),此容器提供了\(contains\)接口帮助查询对象是否存在容器中,特别适用于查询是否被访问过这种操作。

在第二次作业中,message如果类型是0的话,被发送者就会收到这条message,又因为加入这条message需要插到\(message[]\)的头。基于这种描述,我选择了\(LinkedList\),其中的\(addFirst\)接口恰好符合插头的情况。

性能问题与分析

第一次作业

第一次作业中卡性能的应该是\(isCircle(int id1, int id2)\)\(queryBlockSum()\)

\(isCircle\)中我使用了并查集,每次需查询两人的祖先id是否相同。

int id1Father = findFather(id1);
int id2Father = findFather(id2);
return id1Father == id2Father;

\(queryBlockSum()\)中,我并没有在此方法内跑完所有的步骤,而是将其运算分散在了其他方法内,全程维护一个连通块数目,比如在加一个新人的时候,连通块数目加一;加关系的时候,查询两人的祖先id是否相同,若不同则连通块数目减一。通过这样的方法维护一个连通块数目。

第二次作业

第二次作业中比较卡性能的是\(Group\)类中的\(getValueSum()\)方法。如果通过二重循环遍历Group中的所有人员,时间复杂度则为\(O(n²)\)。比较简单去避免TLE而又不需每次去维护这个值的方法便是只遍历一次Group中的所有人员,但是需要遍历每一个人的\(Person[] acquaintance\),这样只需要遍历所有的边即可完成计算valuesum的目的。

for (Person item : people.values()) {
    MyPerson item1 = (MyPerson) item;
    for (Person item2 : item1.myGetAcquaintance().values()) {
        if (hasPerson(item2)) {
            sum += item1.queryValue(item2);
        }
    }
}

第三次作业

第三次作业中比较卡性能的是\(Network\)类中的\(sendIndirectMessage(int id)\)方法。如果使用\(O(n²)\)的迪杰斯特拉最短路径,则很可能会被强测hack。此时就可以通过使用优先队列进行优化,其实使用优先队列要方便不少,因为java中已经提供了优先队列容器\(PriorityQueue\),只需要在此基础上添加比较函数即可。另外记录距离的容器我一开始使用了Treemap,在别人的评测机下发现Hashmap性能要优化不少。虽然我考虑到了\(O(n²)\)的hack数据,但是在强测中我还是倒在了第六个强测点(卡\(O(n²)\)算法的)。我百思不得其解,与算法竞赛的室友讨论了一下,得出的结果是算法时间复杂度确实小了,但是由于我一开始使用的Treemap容器等原因导致常数大了。(下面是迪杰斯特拉最短路径中使用的容器)

Comparator<Pair<Integer,Integer>> cmp = Comparator.comparingInt(Pair::getKey);
Queue<Pair<Integer,Integer>> queue = new PriorityQueue<>(cmp);
HashMap<Integer, Integer> distance = new HashMap<>();
Set<Integer> visited = new HashSet<>();

作业架构设计

image

  • 这个单元作业由于JML的原因,整体架构较为固定。
  • \(MyEmojiMessage\)\(MyRedEnvelopeMessage\)\(MyNoticeMessage\)类继承\(MyMessage\),只需要实现各自相应的get函数即可。
  • 图模型方面:这单元中,Person作为节点,每一个Person中的acquaintance是其相邻的点,value是其对应的边权,因此在Network类中的\(addPerson()\)\(addRelation()\)相当于加点和加边。Group类我的理解是点集,要说明的是我并没有在Group类中实现特殊的维护(在互测中,部分同学实现了每次\(addRelation()\)都会在相应的Group中维护一下valuesum和mean,我并没有这样的实现),因此Group类只实现了基本的add和delete,并没有实现特殊的构建与维护。

感想与体会

本单元主要学习了JML的阅读与面向接口编程。

此单元的作业难度并不大,但是考虑到在实际开发的情况依靠JML写出程序的能力虽然是必要的,但是仍需要有更加形式化的语言来描述程序功能。

更加具体来说,这单元还学习了并查集和堆优化的迪杰斯特拉最短路径算法,其实更加抽象地来说在设计方法选择算法的时候逐渐习惯思考时间复杂度的问题了。

posted @ 2021-05-28 14:18  z邓彬z  阅读(61)  评论(0)    收藏  举报