OO第三次总结性作业

第三次总结性作业

第一次作业

一、基于UML和度量的架构分析

整个代码的大体框架如图所示,其中 UnionFindSet 和 Trie 是为了实现 JML 规格在MyNetWork中所新增的辅助类,由于是按JML编写的,这里就不作赘述。

Method CogC ev(G) iv(G) v(G)
Main.main(String[]) 0 1 1 1
MyEqualPersonIdException.MyEqualPersonIdException(int) 2 1 2 2
MyEqualPersonIdException.print() 0 1 1 1
MyEqualRelationException.MyEqualRelationException(int,int) 8 1 4 5
MyEqualRelationException.print() 0 1 1 1
MyNetwork.MyNetwork() 0 1 1 1
MyNetwork.addPerson(Person) 1 2 2 2
MyNetwork.addRelation(int,int,int) 3 4 3 4
MyNetwork.compareName(int,int) 2 3 2 3
MyNetwork.contains(int) 0 1 1 1
MyNetwork.getPerson(int) 2 2 2 2
MyNetwork.insert(Person) 1 1 2 2
MyNetwork.isCircle(int,int) 2 3 2 3
MyNetwork.queryBlockSum() 0 1 1 1
MyNetwork.queryNameRank(int) 2 2 2 3
MyNetwork.queryPeopleSum() 0 1 1 1
MyNetwork.queryValue(int,int) 3 4 3 4
MyPerson.MyPerson(int,String,int) 0 1 1 1
MyPerson.compareTo(Person) 0 1 1 1
MyPerson.equals(Object) 3 2 2 3
MyPerson.getAge() 0 1 1 1
MyPerson.getId() 0 1 1 1
MyPerson.getName() 0 1 1 1
MyPerson.isLinked(Person) 1 1 2 2
MyPerson.link(Person,int) 0 1 1 1
MyPerson.queryValue(Person) 1 2 1 2
MyPersonIdNotFoundException.MyPersonIdNotFoundException(int) 2 1 2 2
MyPersonIdNotFoundException.print() 0 1 1 1
MyRelationNotFoundException.MyRelationNotFoundException(int,int) 8 1 4 5
MyRelationNotFoundException.print() 0 1 1 1
Trie.Trie() 0 1 1 1
Trie.add() 0 1 1 1
Trie.addend() 0 1 1 1
Trie.createSon(int) 1 1 2 2
Trie.getEnd() 0 1 1 1
Trie.getSon(int) 0 1 1 1
Trie.getSum() 0 1 1 1
Trie.getSum(int) 3 3 2 3
UnionFindSet.UnionFindSet() 0 1 1 1
UnionFindSet.getFather(int) 6 5 3 5
UnionFindSet.getUnionNum() 0 1 1 1
UnionFindSet.inUnion(int,int) 0 1 1 1
UnionFindSet.insert(int) 0 1 1 1
UnionFindSet.link(int,int) 1 2 1 2
Class OCavg OCmax WMC
Main 1 1 1
MyEqualPersonIdException 1.5 2 3
MyEqualRelationException 3 5 6
MyNetwork 2.25 4 27
MyPerson 1.22 2 11
MyPersonIdNotFoundException 1.5 2 3
MyRelationNotFoundException 3 5 6
Trie 1.38 3 11
UnionFindSet 1.83 5 11

可以看到,在基于规格的实现下,方法和类的复杂度都得到了极好的控制,没有出现个别函数复杂度过高的情况。

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

在实现规格的过程中,我基本采取了先暴力实现规格要求,再分析时间复杂度对有必要的部分采取优化的方法。

(一)容器的选择和使用

在容器的选择上,为了使用的方便和可扩展性,我大量使用了 HashMap 这个效率较高的容器来对信息和缓存进行存储。而在 HashSet 和 HashMap 的选择中,个人更倾向于 HashMap,由于 HashSet 的底层实现是通过 HashMap 的,因此我们完全有理由用 HashMap 来保存更多的信息,便于未来的扩展。

除此之外,在有特殊遍历需求的地方,我分别使用了LinkedList 和 TreeSet 进行维护,在性能问题分析中我会重点介绍。

public class MyNetwork implements Network {
    private HashMap<Integer, Person> people;
    private Trie root;
    private UnionFindSet ufs;
}
public class MyPerson implements Person {
    private int id;
    private String name;
    private int age;
    private HashMap<Integer, Person> acquaintance;
    private HashMap<Integer, Integer> value;
}
public class UnionFindSet {
    private int unionNum;
    private HashMap<Integer, Integer> parent;
}
public class Trie {
    private TreeMap<Integer, Trie> son;
    private int sum;
    private int end;
}

(二)性能问题分析

经过分析我们不难发现,像queryValue、queryPeopleSum等一系列操作,都是我们可以通过 HashMap 的一系列操作简单实现的。而本次作业的性能瓶颈主要集中于 isCircle 和 queryNameRank 两个部分上,如果 isCircle 我们按规格通过 DFS 或 BFS 暴力实现,每次的复杂度是 O(人数) 的。而 queryNameRank 如果我们采取直接遍历所有人比较名字的方法,则复杂度是 O(Σ 名字长度) 的,在实际互测中也发现不少人有采取这种方式并且被 hack TLE 的。那么接下来我就简单讲讲 Trie 和 UnionFindSet 如何维护这两种操作的。

1、isCircle

isCircle 操作有着极好的性质,那就是点和点之间关联的边是不会被删除的(虽然这和我们印象中的人际关系有所不符),因此对于这种操作我们可以采取并查集来维护。

具体的实现就是,每个人维护自己的一个父亲 (parent) ,且初始时自己的父亲是自己(同时可以初始化当前的连通块数量)。

public void insert(int id) {
    parent.put(id, id);
    ++unionNum;
}

而查找操作就在于,我们将每一个集合中,深度最浅的父亲作为这个集合的代表,只要它是相同的,则代表两点在同一个集合中。

private int getFather(int id) {
    int root = id;
    while (true) {
        int father = parent.get(root);
        if (father == root) { break; }
        root = father;
    }
    int now = id;
    while (true) {
        int father = parent.get(now);
        if (father == now) { break; }
        parent.put(now, root); //路径压缩
        now = father;
    }
    return root;
}
public boolean inUnion(int id1, int id2) {
    int f1 = getFather(id1);
    int f2 = getFather(id2);
    return f1 == f2;
}

需要注意的是,我们这里为了防止爆栈,使用了非递归的方法来实现并查集操作,并且为了提升效率,我们使用了路径压缩的操作,即在一次更新之后,把这条路径上所有点的父亲都改为当前的根。显然,一个点如果非根,只会被压缩一次,因此这个操作是 O(n) 的。判断在不在一个集合就是比较两个人的父亲。

public void link(int id1, int id2) {
    int f1 = getFather(id1);
    int f2 = getFather(id2);
    if (f1 == f2) { return; }
    --unionNum;
    parent.put(f1, f2);
}

每次添加一条边时,我们就看两点是否在同一集合,如果在,那么我们不需要对集合操作;否则,我们就更新父亲关系,调整集合数量。

2、queryNameRank

为了快速的处理这个名次,一种比较显然的方式是通过平衡树来对名次进行维护,但平衡树的实现还是比较复杂。因此,我们退而求其次地用了常数较大的 Trie 树也就是字典树来实现,同样收获了不错的效果。

private void insert(Person person) {
    Trie now = root;
    String name = person.getName();
    int len = name.length();
    for (int i = 0; i < len; ++i) {
        int t = name.charAt(i);
        now = now.createSon(t);
        now.add();
    }
    now.addend();
}
public Trie getSon(int i) {
    return son.get(i);
}

public Trie createSon(int i) {
    if (!son.containsKey(i)) {
        son.put(i, new Trie());
    }
    return son.get(i);
}
public void add() {
    ++sum;
}

public void addend() {
    ++end;
}

private int getEnd() {
    return end;
}

private int getSum() {
    return sum;
}

Trie 树的建立就和一般的字典树一样,我们统计了每个点子树中有多少字符串,同时统计了每个点结束的字符串个数。]

public int getSum(int asc) {
    int sum = getEnd();
    Iterator<Map.Entry<Integer, Trie>> entries = son.entrySet().iterator();
    while (entries.hasNext()) {
        Map.Entry<Integer, Trie> entry = entries.next();
        int key = entry.getKey();
        if (key >= asc) { break; }
        Trie now = entry.getValue();
        sum += now.getSum();
    }
    return sum;
}

查询时就体现了我们选择 TreeSet 的优势,我们按照键值有序的方式,排名就能加上所有当前位小于查询串当前位的字符串。当然,我们不能忘记统计当时正好结束的字串。

public int queryNameRank(int id) throws PersonIdNotFoundException {
    if (!people.containsKey(id)) {
        throw new MyPersonIdNotFoundException(id);
    }
    Trie now = root;
    String name = people.get(id).getName();
    int len = name.length();
    int rank = 0;
    for (int i = 0; i < len; ++i) {
        int t = name.charAt(i);
        rank += now.getSum(t);
        now = now.getSon(t);
    }
    return rank + 1;
}

最后在字典树上从上往下添加每个分支的值即可,由于有效的可视字符只有 94 个,这种方法有较好的效率。

3、addMessage

在sendMessage之后,需要给 Person 类添加 Message,而且是加在最前面,因此我们使用 LinkedList 来 O(1) 实现这个操作。

public void addMessage(Message message) {
    messages.addFirst(message);
}

第二次作业

一、基于UML和度量的架构分析

从 UML 来看,整体的结构没有大的改变,新增了 MyGroup 和 MyMessage 以及一系列的异常。

此次作业由于实现的功能较为简单,没有新增别的类来辅助操作。

Method CogC ev(G) iv(G) v(G)
Main.main(String[]) 0 1 1 1
MyEqualGroupIdException.MyEqualGroupIdException(int) 2 1 2 2
MyEqualGroupIdException.print() 0 1 1 1
MyEqualMessageIdException.MyEqualMessageIdException(int) 2 1 2 2
MyEqualMessageIdException.print() 0 1 1 1
MyEqualPersonIdException.MyEqualPersonIdException(int) 2 1 2 2
MyEqualPersonIdException.print() 0 1 1 1
MyEqualRelationException.MyEqualRelationException(int,int) 8 1 4 5
MyEqualRelationException.print() 0 1 1 1
MyGroup.MyGroup(int) 0 1 1 1
MyGroup.addPerson(Person) 1 1 2 2
MyGroup.addSocialValue(int) 1 1 2 2
MyGroup.addValueSum(int) 0 1 1 1
MyGroup.delPerson(Person) 1 1 2 2
MyGroup.equals(Object) 3 2 2 3
MyGroup.getAgeMean() 1 2 1 2
MyGroup.getAgeVar() 1 2 1 2
MyGroup.getId() 0 1 1 1
MyGroup.getSize() 0 1 1 1
MyGroup.getValueSum() 0 1 1 1
MyGroup.hasPerson(Person) 0 1 1 1
MyGroupIdNotFoundException.MyGroupIdNotFoundException(int) 2 1 2 2
MyGroupIdNotFoundException.print() 0 1 1 1
MyMessage.MyMessage(int,int,Person,Group) 0 1 1 1
MyMessage.MyMessage(int,int,Person,Person) 0 1 1 1
MyMessage.equals(Object) 3 2 2 3
MyMessage.getGroup() 0 1 1 1
MyMessage.getId() 0 1 1 1
MyMessage.getPerson1() 0 1 1 1
MyMessage.getPerson2() 0 1 1 1
MyMessage.getSocialValue() 0 1 1 1
MyMessage.getType() 0 1 1 1
MyMessageIdNotFoundException.MyMessageIdNotFoundException(int) 2 1 2 2
MyMessageIdNotFoundException.print() 0 1 1 1
MyNetwork.MyNetwork() 0 1 1 1
MyNetwork.addGroup(Group) 2 2 2 2
MyNetwork.addMessage(Message) 5 3 4 4
MyNetwork.addPerson(Person) 1 2 2 2
MyNetwork.addRelation(int,int,int) 3 4 3 4
MyNetwork.addToGroup(int,int) 7 5 3 5
MyNetwork.compareName(int,int) 2 3 2 3
MyNetwork.contains(int) 0 1 1 1
MyNetwork.containsMessage(int) 0 1 1 1
MyNetwork.delFromGroup(int,int) 5 4 3 4
MyNetwork.getGroup(int) 2 2 2 2
MyNetwork.getMessage(int) 2 2 2 2
MyNetwork.getPerson(int) 2 2 2 2
MyNetwork.insert(Person) 1 1 2 2
MyNetwork.isCircle(int,int) 2 3 2 3
MyNetwork.queryBlockSum() 0 1 1 1
MyNetwork.queryGroupAgeMean(int) 2 2 2 2
MyNetwork.queryGroupAgeVar(int) 2 2 2 2
MyNetwork.queryGroupPeopleSum(int) 2 2 2 2
MyNetwork.queryGroupSum() 0 1 1 1
MyNetwork.queryGroupValueSum(int) 2 2 2 2
MyNetwork.queryNameRank(int) 2 2 2 3
MyNetwork.queryPeopleSum() 0 1 1 1
MyNetwork.queryReceivedMessages(int) 2 2 2 2
MyNetwork.querySocialValue(int) 2 2 2 2
MyNetwork.queryValue(int,int) 3 4 3 4
MyNetwork.sendMessage(int) 13 4 9 9
MyPerson.MyPerson(int,String,int) 0 1 1 1
MyPerson.addGroup(Group) 0 1 1 1
MyPerson.addMessage(Message) 0 1 1 1
MyPerson.addSocialValue(int) 0 1 1 1
MyPerson.addValueSum(Person) 3 1 3 3
MyPerson.compareTo(Person) 0 1 1 1
MyPerson.delGroup(Group) 0 1 1 1
MyPerson.equals(Object) 3 2 2 3
MyPerson.getAge() 0 1 1 1
MyPerson.getId() 0 1 1 1
MyPerson.getMessages() 0 1 1 1
MyPerson.getName() 0 1 1 1
MyPerson.getReceivedMessages() 3 3 2 3
MyPerson.getSocialValue() 0 1 1 1
MyPerson.isLinked(Person) 1 1 2 2
MyPerson.link(Person,int) 0 1 1 1
MyPerson.queryValue(Person) 1 2 1 2
MyPersonIdNotFoundException.MyPersonIdNotFoundException(int) 2 1 2 2
MyPersonIdNotFoundException.print() 0 1 1 1
MyRelationNotFoundException.MyRelationNotFoundException(int,int) 8 1 4 5
MyRelationNotFoundException.print() 0 1 1 1
Trie.Trie() 0 1 1 1
Trie.add() 0 1 1 1
Trie.addend() 0 1 1 1
Trie.createSon(int) 1 1 2 2
Trie.getEnd() 0 1 1 1
Trie.getSon(int) 0 1 1 1
Trie.getSum() 0 1 1 1
Trie.getSum(int) 3 3 2 3
UnionFindSet.UnionFindSet() 0 1 1 1
UnionFindSet.getFather(int) 6 5 3 5
UnionFindSet.getUnionNum() 0 1 1 1
UnionFindSet.inUnion(int,int) 0 1 1 1
UnionFindSet.insert(int) 0 1 1 1
UnionFindSet.link(int,int) 1 2 1 2
Class OCavg OCmax WMC
Main 1 1 1
MyEqualGroupIdException 1.5 2 3
MyEqualMessageIdException 1.5 2 3
MyEqualPersonIdException 1.5 2 3
MyEqualRelationException 3 5 6
MyGroup 1.5 2 18
MyGroupIdNotFoundException 1.5 2 3
MyMessage 1.11 2 10
MyMessageIdNotFoundException 1.5 2 3
MyNetwork 2.41 6 65
MyPerson 1.35 3 23
MyPersonIdNotFoundException 1.5 2 3
MyRelationNotFoundException 3 5 6
Trie 1.38 3 11
UnionFindSet 1.83 5 11

可以看到,除了异常情况较多的 sendMessage,函数的复杂度都较为合适。

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

(一)容器的选择和使用

容器的选择和上一次作业没有较大的区别,主要还是以 HashMap 为主。其中,为了实现在队首插入,取前4个 Message 的功能,Person 中的容器使用了 LinkedList,这样,每次操作都是 O(1) 的。我们还能够看到,Person 中新增了一个 group 的容器,这是 JML 之外的,这是我们为了便于维护而新增的容器。

public class MyNetwork implements Network {
    private HashMap<Integer, Person> people;
    private HashMap<Integer, Group> groups;
    private HashMap<Integer, Message> messages;
    private Trie root;
    private UnionFindSet ufs;
}
public class MyGroup implements Group {
    private int id;
    private HashMap<Integer, Person> people;
    private int ageSum;
    private int ageQSum;
    private int valueSum;
}
public class MyPerson implements Person {
    private int id;
    private String name;
    private int age;
    private HashMap<Integer, Person> acquaintance;
    private HashMap<Integer, Integer> value;
    private int socialValue;
    private LinkedList<Message> messages;
    private HashMap<Integer, Group> groups;
}
public class MyMessage implements Message {
    private int id;
    private int socialValue;
    private int type;
    private Person person1;
    private Person person2;
    private Group group;
}

(二)性能问题分析

经过分析我们可以发现,第二次作业相对于第一次作业更为简单,主要的复杂度集中在 queryValueSum 和 queryAge 等一系列操作中。

其中,queryAge 的一系列操作可以通过简单的数学公式变形简单维护,这里我们就不赘述。

接下来我们主要讲一讲 queryValueSum 的操作。

首先一种暴力的方式,直接两两枚举 group 中所有的人,时间复杂度 O(n²) 。在互测中 TLE 翻车的绝大部分人应该都是使用的这种做法。

那么接下来我们想想怎么比较快地维护这个值:我们可以很容易的发现,这个值并不需要实时计算,而是可以通过集合关系的变化提前计算出来提高速度的。

显然在集合添加或者删除个人时,我们可以直接枚举集合中的所有人 O(n) 解决这个问题。

public void addPerson(Person person) {
    people.put(person.getId(), person);
    ageSum += person.getAge();
    ageQSum += person.getAge() * person.getAge();
    for (Integer id : people.keySet()) {
        Person peo = people.get(id);
        valueSum += peo.queryValue(person);
    }
}
public void delPerson(Person person) {
    ageSum -= person.getAge();
    ageQSum -= person.getAge() * person.getAge();
    people.remove(person.getId());
    for (Integer id : people.keySet()) {
        Person peo = people.get(id);
        valueSum -= peo.queryValue(person);
    }
}

需要注意的是,在外面人与人添加关系时,可能也会影响某个集合的值,这也是为什么我们需要在 Person 里面存储 Group 的信息。

//MyPerson
public void addValueSum(Person person) {
    for (Integer id : groups.keySet()) {
        Group group = groups.get(id);
        if (group.hasPerson(person)) { //如果两人在同一个组里
            ((MyGroup)group).addValueSum(queryValue(person));
        }
    }
}

第三次作业

一、基于UML和度量的架构分析

从 UML 来看,整体架构没有大的变化,不过在 MyMessage 下新增了三个子类以及对应的 Exception,同时新增了 Distance 类来便于我们Dijkstra 的实现。

Method CogC ev(G) iv(G) v(G)
Distance.Distance(Integer,Person) 0 1 1 1
Distance.compareTo(Object) 0 1 1 1
Distance.getDistance() 0 1 1 1
Distance.getPerson() 0 1 1 1
Main.main(String[]) 0 1 1 1
MyEmojiIdNotFoundException.MyEmojiIdNotFoundException(int) 2 1 2 2
MyEmojiIdNotFoundException.print() 0 1 1 1
MyEmojiMessage.MyEmojiMessage(int,int,Person,Group) 0 1 1 1
MyEmojiMessage.MyEmojiMessage(int,int,Person,Person) 0 1 1 1
MyEmojiMessage.addMessage(HashMap<Integer, Message>,HashMap<Integer, Integer>) 1 2 2 2
MyEmojiMessage.getEmojiId() 0 1 1 1
MyEmojiMessage.sendIndirectMessage(HashMap<Integer, Message>,HashMap<Integer, Integer>) 0 1 1 1
MyEmojiMessage.sendMessage(HashMap<Integer, Message>,HashMap<Integer, Integer>) 3 1 4 4
MyEqualEmojiIdException.MyEqualEmojiIdException(int) 2 1 2 2
MyEqualEmojiIdException.print() 0 1 1 1
MyEqualGroupIdException.MyEqualGroupIdException(int) 2 1 2 2
MyEqualGroupIdException.print() 0 1 1 1
MyEqualMessageIdException.MyEqualMessageIdException(int) 2 1 2 2
MyEqualMessageIdException.print() 0 1 1 1
MyEqualPersonIdException.MyEqualPersonIdException(int) 2 1 2 2
MyEqualPersonIdException.print() 0 1 1 1
MyEqualRelationException.MyEqualRelationException(int,int) 8 1 4 5
MyEqualRelationException.print() 0 1 1 1
MyGroup.MyGroup(int) 0 1 1 1
MyGroup.addMoney(int) 1 1 2 2
MyGroup.addPerson(Person) 1 1 2 2
MyGroup.addSocialValue(int) 1 1 2 2
MyGroup.addValueSum(int) 0 1 1 1
MyGroup.delPerson(Person) 1 1 2 2
MyGroup.equals(Object) 3 2 2 3
MyGroup.getAgeMean() 1 2 1 2
MyGroup.getAgeVar() 1 2 1 2
MyGroup.getId() 0 1 1 1
MyGroup.getSize() 0 1 1 1
MyGroup.getValueSum() 0 1 1 1
MyGroup.hasPerson(Person) 0 1 1 1
MyGroupIdNotFoundException.MyGroupIdNotFoundException(int) 2 1 2 2
MyGroupIdNotFoundException.print() 0 1 1 1
MyMessage.MyMessage(int,int,Person,Group) 0 1 1 1
MyMessage.MyMessage(int,int,Person,Person) 0 1 1 1
MyMessage.addMessage(HashMap<Integer, Message>,HashMap<Integer, Integer>) 2 2 3 3
MyMessage.dijkstra() 10 4 4 6
MyMessage.equals(Object) 3 2 2 3
MyMessage.getDis(int) 0 1 1 1
MyMessage.getGroup() 0 1 1 1
MyMessage.getId() 0 1 1 1
MyMessage.getPerson1() 0 1 1 1
MyMessage.getPerson2() 0 1 1 1
MyMessage.getSocialValue() 0 1 1 1
MyMessage.getType() 0 1 1 1
MyMessage.sendIndirectMessage(HashMap<Integer, Message>,HashMap<Integer, Integer>) 0 1 1 1
MyMessage.sendMessage(HashMap<Integer, Message>,HashMap<Integer, Integer>) 7 3 8 8
MyMessageIdNotFoundException.MyMessageIdNotFoundException(int) 2 1 2 2
MyMessageIdNotFoundException.print() 0 1 1 1
MyNetwork.MyNetwork() 0 1 1 1
MyNetwork.addGroup(Group) 2 2 2 2
MyNetwork.addMessage(Message) 2 2 2 2
MyNetwork.addPerson(Person) 1 2 2 2
MyNetwork.addRelation(int,int,int) 3 4 3 4
MyNetwork.addToGroup(int,int) 7 5 3 5
MyNetwork.compareName(int,int) 2 3 2 3
MyNetwork.contains(int) 0 1 1 1
MyNetwork.containsEmojiId(int) 0 1 1 1
MyNetwork.containsMessage(int) 0 1 1 1
MyNetwork.delFromGroup(int,int) 5 4 3 4
MyNetwork.deleteColdEmoji(int) 11 1 8 8
MyNetwork.getGroup(int) 2 2 2 2
MyNetwork.getMessage(int) 2 2 2 2
MyNetwork.getPerson(int) 2 2 2 2
MyNetwork.insert(Person) 1 1 2 2
MyNetwork.isCircle(int,int) 2 3 2 3
MyNetwork.queryBlockSum() 0 1 1 1
MyNetwork.queryGroupAgeMean(int) 2 2 2 2
MyNetwork.queryGroupAgeVar(int) 2 2 2 2
MyNetwork.queryGroupPeopleSum(int) 2 2 2 2
MyNetwork.queryGroupSum() 0 1 1 1
MyNetwork.queryGroupValueSum(int) 2 2 2 2
MyNetwork.queryMoney(int) 2 2 2 2
MyNetwork.queryNameRank(int) 2 2 2 3
MyNetwork.queryPeopleSum() 0 1 1 1
MyNetwork.queryPopularity(int) 2 2 2 2
MyNetwork.queryReceivedMessages(int) 2 2 2 2
MyNetwork.querySocialValue(int) 2 2 2 2
MyNetwork.queryValue(int,int) 3 4 3 4
MyNetwork.sendIndirectMessage(int) 4 3 3 5
MyNetwork.sendMessage(int) 2 2 2 2
MyNetwork.storeEmojiId(int) 1 2 1 2
MyNoticeMessage.MyNoticeMessage(int,String,Person,Group) 0 1 1 1
MyNoticeMessage.MyNoticeMessage(int,String,Person,Person) 0 1 1 1
MyNoticeMessage.getString() 0 1 1 1
MyPerson.MyPerson(int,String,int) 0 1 1 1
MyPerson.addGroup(Group) 0 1 1 1
MyPerson.addMessage(Message) 0 1 1 1
MyPerson.addMoney(int) 0 1 1 1
MyPerson.addSocialValue(int) 0 1 1 1
MyPerson.addValueSum(Person) 3 1 3 3
MyPerson.compareTo(Person) 0 1 1 1
MyPerson.delGroup(Group) 0 1 1 1
MyPerson.equals(Object) 3 2 2 3
MyPerson.getAge() 0 1 1 1
MyPerson.getId() 0 1 1 1
MyPerson.getIterator() 0 1 1 1
MyPerson.getMessages() 0 1 1 1
MyPerson.getMoney() 0 1 1 1
MyPerson.getName() 0 1 1 1
MyPerson.getReceivedMessages() 3 3 2 3
MyPerson.getSocialValue() 0 1 1 1
MyPerson.isLinked(Person) 1 1 2 2
MyPerson.link(Person,int) 0 1 1 1
MyPerson.queryValue(Person) 1 2 1 2
MyPersonIdNotFoundException.MyPersonIdNotFoundException(int) 2 1 2 2
MyPersonIdNotFoundException.print() 0 1 1 1
MyRedEnvelopeMessage.MyRedEnvelopeMessage(int,int,Person,Group) 0 1 1 1
MyRedEnvelopeMessage.MyRedEnvelopeMessage(int,int,Person,Person) 0 1 1 1
MyRedEnvelopeMessage.getMoney() 0 1 1 1
MyRedEnvelopeMessage.sendIndirectMessage(HashMap<Integer, Message>,HashMap<Integer, Integer>) 0 1 1 1
MyRedEnvelopeMessage.sendMessage(HashMap<Integer, Message>,HashMap<Integer, Integer>) 3 1 4 4
MyRelationNotFoundException.MyRelationNotFoundException(int,int) 8 1 4 5
MyRelationNotFoundException.print() 0 1 1 1
Trie.Trie() 0 1 1 1
Trie.add() 0 1 1 1
Trie.addend() 0 1 1 1
Trie.createSon(int) 1 1 2 2
Trie.getEnd() 0 1 1 1
Trie.getSon(int) 0 1 1 1
Trie.getSum() 0 1 1 1
Trie.getSum(int) 3 3 2 3
UnionFindSet.UnionFindSet() 0 1 1 1
UnionFindSet.getFather(int) 6 5 3 5
UnionFindSet.getUnionNum() 0 1 1 1
UnionFindSet.inUnion(int,int) 0 1 1 1
UnionFindSet.insert(int) 0 1 1 1
UnionFindSet.link(int,int) 1 2 1 2
Class OCavg OCmax WMC
Distance 1 1 4
Main 1 1 1
MyEmojiIdNotFoundException 1.5 2 3
MyEmojiMessage 1.5 3 9
MyEqualEmojiIdException 1.5 2 3
MyEqualGroupIdException 1.5 2 3
MyEqualMessageIdException 1.5 2 3
MyEqualPersonIdException 1.5 2 3
MyEqualRelationException 3 5 6
MyGroup 1.54 2 20
MyGroupIdNotFoundException 1.5 2 3
MyMessage 1.79 6 25
MyMessageIdNotFoundException 1.5 2 3
MyNetwork 2.36 8 78
MyNoticeMessage 1 1 3
MyPerson 1.3 3 26
MyPersonIdNotFoundException 1.5 2 3
MyRedEnvelopeMessage 1.4 3 7
MyRelationNotFoundException 3 5 6
Trie 1.38 3 11
UnionFindSet 1.83 5 11

可以看到,除了为了跑最短路而实现的 dijkstra 之外,方法的复杂度都较小。而上一次复杂度较大的sendMessage,在这一次的作业中,由于我们在类的继承方面的设计,复杂度有明显的减小。

//MyMessage
public void sendMessage(HashMap<Integer, Message> messages,
                        HashMap<Integer, Integer> emojiHeatList)
                        throws RelationNotFoundException, PersonIdNotFoundException {
    if (this.getType() == 0 && !person1.isLinked(person2)) {
        throw new MyRelationNotFoundException(person1.getId(), person2.getId());
    }
    if (this.getType() == 1 && !group.hasPerson(person1)) {
        throw new MyPersonIdNotFoundException(person1.getId());
    }
    if (this.getType() == 0 && !person1.equals(person2)) {
        person1.addSocialValue(this.getSocialValue());
        person2.addSocialValue(this.getSocialValue());
        messages.remove(this.id);
        ((MyPerson)person2).addMessage(this);
    }
    if (this.getType() == 1) {
        ((MyGroup)group).addSocialValue(this.getSocialValue());
        messages.remove(this.id);
    }
}

public int sendIndirectMessage(HashMap<Integer, Message> messages,
                                   HashMap<Integer, Integer> emojiHeatList) {
        messages.remove(id);
        person1.addSocialValue(this.getSocialValue());
        person2.addSocialValue(this.getSocialValue());
        ((MyPerson)person2).addMessage(this);
        return dijkstra();
}
//MyRedEnvelopeMessage
public void sendMessage(HashMap<Integer, Message> messages,
                        HashMap<Integer, Integer> emojiHeatList)
        throws RelationNotFoundException, PersonIdNotFoundException {
    super.sendMessage(messages, emojiHeatList);
    Person person1 = this.getPerson1();
    Person person2 = this.getPerson2();
    Group group = this.getGroup();
    if (this.getType() == 0 && !person1.equals(person2)) {
        person1.addMoney(-money);
        person2.addMoney(money);
    }
    if (this.getType() == 1) {
        int deltaMoney = money / group.getSize();
        person1.addMoney(-deltaMoney * group.getSize());
        ((MyGroup)group).addMoney(deltaMoney);
    }
}

public int sendIndirectMessage(HashMap<Integer, Message> messages,
                               HashMap<Integer, Integer> emojiHeatList) {
    this.getPerson1().addMoney(-money);
    this.getPerson2().addMoney(money);
    return super.sendIndirectMessage(messages, emojiHeatList);
}
//MyEmojiMessage
public void sendMessage(HashMap<Integer, Message> messages,
                        HashMap<Integer, Integer> emojiHeatList)
        throws RelationNotFoundException, PersonIdNotFoundException {
    super.sendMessage(messages, emojiHeatList);
    Person person1 = this.getPerson1();
    Person person2 = this.getPerson2();
    if (this.getType() == 0 && !person1.equals(person2)) {
        int heat = emojiHeatList.get(emojiId);
        emojiHeatList.put(emojiId, heat + 1);
    }
    if (this.getType() == 1) {
        int heat = emojiHeatList.get(emojiId);
        emojiHeatList.put(emojiId, heat + 1);
    }
}

public int sendIndirectMessage(HashMap<Integer, Message> messages,
                               HashMap<Integer, Integer> emojiHeatList) {
    int heat = emojiHeatList.get(emojiId);
    emojiHeatList.put(emojiId, heat + 1);
    return super.sendIndirectMessage(messages, emojiHeatList);
}

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

(一)容器的选择和使用

容器还是和上次一样,主要以 HashMap 为主,在局部为了实现 dijkstra 而采用了 PriorityQueue。

public class MyNetwork implements Network {
    private HashMap<Integer, Person> people;
    private HashMap<Integer, Group> groups;
    private HashMap<Integer, Message> messages;
    private HashMap<Integer, Integer> emojiHeatList;
    private Trie root;
    private UnionFindSet ufs;
}
public class MyMessage implements Message {
    private int id;
    private int socialValue;
    private int type;
    private Person person1;
    private Person person2;
    private Group group;
    private PriorityQueue<Distance> pq;
    private HashMap<Integer, Integer> minDis;
    private HashSet<Integer> visit;
    private static int INF = 0x7fffffff;
}
public class MyPerson implements Person {
    private int id;
    private String name;
    private int age;
    private HashMap<Integer, Person> acquaintance;
    private HashMap<Integer, Integer> value;
    private int money;
    private int socialValue;
    private LinkedList<Message> messages;
    private HashMap<Integer, Group> groups;
}
public class MyEmojiMessage extends MyMessage implements EmojiMessage {
    private int emojiId;
}
public class MyRedEnvelopeMessage extends MyMessage implements RedEnvelopeMessage {
    private int money;
}

(二)性能问题分析

此次作业的难度依旧较小,由于有求最短路这个复杂度极大的操作,因此别的操作 O(n) 暴力实现对我们整体的复杂度没有明显的影响,这里我们就不作赘述。

这里我们主要讲讲最短路的做法。

首先,许多同学直接采用了大一下课上讲的 O(n²) 的最短路,这必然会导致 TLE 貌似有人也卡过了

其次,使用 SPFA 虽然好写,但终究有被卡的风险。

因此,我们使用 O(nlogn) 的堆优化 Dijkstra 来解决这个问题,具体思路和一般的最短路没有区别,只不过在找当前距离最小的点时,我们使用了堆来进行这个操作,而 JAVA 有自带堆的库,因此没有什么难度。PS:区别于 C++,JAVA 的堆默认是小根堆,需要注意。

private int dijkstra() {
        pq.clear();
        minDis.clear();
        visit.clear();
        minDis.put(person1.getId(), 0);
        pq.add(new Distance(0, person1)); //将起始的距离初始化为0
        while (!pq.isEmpty()) {
            Distance dis = pq.poll();//取出并弹出堆顶
            Person person = dis.getPerson();
            int distance = dis.getDistance();
            if (person.equals(person2)) { //已经访问到person2
                return distance;
            }
            if (visit.contains(person.getId())) {//检查是否已经出堆
                continue;
            }
            visit.add(person.getId());//设置标记
            Iterator<Map.Entry<Integer, Person>> iterator = ((MyPerson)person).getIterator(); //这里我们取出																							Person中关系的迭代器
            while (iterator.hasNext()) {
                Person direction = iterator.next().getValue();
                int oriDistance = getDis(direction.getId());
                if (oriDistance > distance + person.queryValue(direction)) {//更新距离,放入堆
                    int newDistance = distance + person.queryValue(direction);
                    minDis.put(direction.getId(), newDistance);
                    pq.add(new Distance(newDistance, direction));
                }
            }
        }
        return -1;
    }

心得体会

本次作业,在实现上难度较低,整体的重心主要集中在 JML 的阅读和 JUNIT 的单元测试上。通过这一次的作业,个人很明显地感受到了 通过 JML 或者是通过设计文档来合作进行编程的优势,在编程之前,我们能够理清整个程序的思路,了解类与类之间交互的方式,但是在编程过程中,我们可以完全不考虑这些问题,专心地去维护自己的不变式和接口,以满足规格的约束,在有特殊性能要求的情况下,也能够不影响别的部分专心维护。

posted @ 2021-05-28 14:50  L_RUA  阅读(306)  评论(0编辑  收藏  举报