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 或者是通过设计文档来合作进行编程的优势,在编程之前,我们能够理清整个程序的思路,了解类与类之间交互的方式,但是在编程过程中,我们可以完全不考虑这些问题,专心地去维护自己的不变式和接口,以满足规格的约束,在有特殊性能要求的情况下,也能够不影响别的部分专心维护。