ICode9

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

BUAAOO第三单元总结

2021-06-01 10:02:50  阅读:168  来源: 互联网

标签:总结 blocks get int id2 id1 BUAAOO public 单元


第三单元总结博客

设计策略

​ 第九次作业实现的东西并不复杂,只是简单的提供了一个社交系统的必要组成部分,包括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语句没删,导致评测始终不通过的尴尬场景,这也提醒我们良好的架构不仅有利于读懂代码,还有利于后期的修改和维护,这在以后的代码协作过程中十分重要。

标签:总结,blocks,get,int,id2,id1,BUAAOO,public,单元
来源: https://www.cnblogs.com/Nostradamus-zrz/p/14835855.html

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

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

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

ICode9版权所有