301
社区成员
发帖
与我相关
我的任务
分享
第一作业两个比较重要的部分,分别是isCircle(int id1, int id2),queryBlockSum()和queryTripleSum()这三个方法的实现,这三个方法的实现依赖于我们需要去维护一个连通块的数量,和一个三角形数量,由于我们有:添加人、添加边、删除边的操作。如果我们每次都dfs来查找的话,这样的时间复杂度显然是我们不可以接受的,所以我们采用并查集,每次在加人或添加关系时,去维护这个并查集,而在删除关系时,通过dfs染色的方式对并查集进行重建,下面我们来看具体的过程。
add方法;添加关系时调用merge(int id, int id),并且把前者视做后者的父亲,find操作是在找“族长”(感觉这个词很贴切),他会不断的根据存储的值去找父节点,直到父节点的值为本身,说明这个人就是族长,然后我们更新这条搜寻链路上的所有人的父节点都为该族长,这个操作是O(n)的id1与id2之间的关系,那么首先我们将id2的父节点设为自己,然后将id1作为dfs的起始点进行dfs, 在dfs过程中,将与id1相连通的点的父节点设为id1, 之后去看id2的父节点是否为id1,如果是的话,说明两者仍连通,并且并查集已经被重构完成,如果不是的话,说明两者不再连通,我们对id2进行相同的操作,以此来完成并查集的重构。blockSum 添加人的时候++,添加关系的时候,调用find,如果两者的族长相同,那么不变,如果不同,blocksum--,并且merge两个人,删除关系的时候,就按照上面进行并查集的重建,如果仍连通则blocksum不变,否则++triSum 添加或删除关系时,遍历其他所有人,看与这两人是否连接,如果连接,那么trisum++或--public class DisjointSet {
private HashMap<Integer, Integer> parentMap;//前键为当前,后键为父
public DisjointSet() {
this.parentMap = new HashMap<>();
}
public void add(int id) {
if (!parentMap.containsKey(id)) {
parentMap.put(id, id);
}
}
public int find(int id) {
int rep = id;
while (rep != parentMap.get(rep)) {
rep = parentMap.get(rep);
}
int now = id;
while (now != rep) {
int fa = parentMap.get(now);
parentMap.replace(now, rep);
now = fa;
}
return rep;
}
public int merge(int id1, int id2) {
int fa1 = find(id1);
int fa2 = find(id2);
if (fa1 == fa2) {
return -1;
}
parentMap.replace(fa1, fa2);
return 0;
}
public void resetParent(int id, int faId) {
this.parentMap.replace(id, faId);
}
public int askFa(int id) {
return parentMap.get(id);
}
}
这次作业中我们还需要对异常进行计数,包括异常的总数与对应ID的异常数,我的实现方法是,先建立一个计数类Counter,如下:
public Counter() {
this.totalCnt = 0;
this.idCntMap = new HashMap<>();
}
其totalCnt负责整体的计数,而idCntMap 就负责记录每一个id触发的异常的次数
之后在所有异常类中添加如下内容
private static Counter counter;
static {
counter = new Counter();
}
用来实现静态成员变量的初始化。在Java中,静态成员变量属于类级别,不属于任何特定对象实例,也就是说,所有此类的对象共享同一个静态变量。以此用于统计创建该类实例的次数,这样的初始化方式可以确保这个计数在程序的整个生命周期内都是准确的,并且对所有实例可见。

第二次作业整体上架构变化不明显,我们多了一个Tag类,我们依然选择在删边与减边的阶段去维护Tag里的内容。第二次作业的难度整体很小,我就写点易错的点吧。
queryTagAgeVar,在实现这个方法时我依然选择的是维护AgeSum 和AgeSquareSum的值的,但是我们在写这个公式的时候,
/*@ ensures \result == (persons.length == 0? 0:
@ ((\sum int i; 0 <= i && i < persons.length; persons[i].getAge()) / persons.length));
@*/
public /*@ pure @*/ int getAgeMean();
/*@ ensures \result == (persons.length == 0? 0 : ((\sum int i; 0 <= i && i < persons.length;
@ (persons[i].getAge() - getAgeMean()) * (persons[i].getAge() - getAgeMean())) /
@ persons.length));
@*/
public /*@ pure @*/ int getAgeVar();
从这个JML可以知道,getAgeVar的描述中的AgeMean已经向下取整过了因此




第三次作业我们增加了Message类,和几个特殊的Message类,整体来说没啥难点,只需要严格的遵守JML去写就行喽,那就略!
@Before注解设置初始状态,执行操作后使用断言(assertThat)比较预期与实际结果,确保没有非预期变更。注意深克隆一份原始数据作为对照。ensures部分定义了操作后应满足的条件。测试时,需根据这些条件构造测试场景,验证操作完成后系统状态或输出是否满足了这些预期的变化。@Test(expected = Exception.class)注解测试方法,确保在给定异常输入时抛出正确的异常类型。JML单元真的让人很舒适↖(^ω^)↗ ,整体框架已经由规格信息给出,你只管去正确实现就行,感觉JML很适合团队任务(或许在软工这门课可以用到?不懂,大雾),团队可以整体设计好实现一个任务的框架,然后共同写好一个规格,然后再分发给团队的成员去实现,成员实现后先进行Junit测试,再上交代码