301
社区成员
发帖
与我相关
我的任务
分享经历了一个月的多线程的折磨,JML语言好像看上去很友好,不会需要考虑各种其他线程来回跳的问题,而且本人对于这种逻辑比较敏感,学习淑芬跟离散的时候很喜欢这种公式化的证明,所以JML语言看起来感觉很熟悉,所以本单元学习起来没什么大问题。由于本次作业的大部分方法是课程组给出的方法,整个的结构也是课程组给出的,所以不分析程序在逻辑上的复杂度,更多的在本次总结中将会介绍相关的算法运行时的复杂度。
第一次作业让我第一知道了原来java还有异常这种东西(,有一次感受到了java面向对象的特性,同时这种异常更多的是显式异常,正如吴际老师说的那样整个程序基本上不存在隐式异常,而是会让程序进行显式的输出,这也为我以后的程序中的不应该出现的特殊形况的处理指明了很好的方向,而不是像以前那样只会System.err.println.
这次作业如果能看懂JML语言的话基本上不会有什么问题,但是这次作业对于算法复杂度要求很高,一开始我想到了一个分而治之的算法,后来仔细一想这种方法就是大名鼎鼎的并查集,但是与去年的题目不同的是,这次的作业增加了删除关系的相关操作,导致一般的并查集并不能简单地搬过来,虽然有人提出了延迟建立的并查集但是我懒了,最后只用了dfs解决了相关问题,
public boolean dfs(int id1, int id2) {
visited.add(id1);
if (isLinked(id1, id2)) {
return true;
}
for (int id : persons.get(id1).getAcquaintance().keySet()) {
if (!visited.contains(id)) {
if (dfs(id, id2)) {
return true;
}
}
}
// visited.remove(visited.size() - 1);
return false;
}
值得注意的是一开始我会将遍历过的点后退删除,这其实是很不对的。因为如果a不能通过b点到达c点那么他就不能先通过d点再通过b点最后到达c点,这个bug我改了好长时间,在最后打复活赛的时候才发现了这个问题。
public int queryTripleSum() {
sum=0;
for (Integer id1 : persons.keySet()) {
for (Integer id2 : persons.get(id1).getAcquaintance().keySet()) {
if (id1 < id2) {
for (Integer id3 : persons.get(id2).getAcquaintance().keySet()) {
if (id2 < id3) {
if (isLinked(id1, id3)) {
sum++;
}
}
}
}
}
}
return sum;
}
qts的时间复杂度是本次作业时间复杂度最大的地方,有人是建立缓存的方法来减少时间复杂度到o(n^2^),我是通过遍历边的方式来减少时间复杂度到o(m^2^*n)但是在强测的时候遇到完全图毫无意外的崩掉了,但是好在打赢了复活赛。
感觉第三单元最恶心的地方就是junit的编写,由于没有在一开始写自动的数据生成器导致一开始的数据生成强度太弱,而且一开始没有想明白应该怎么判断前后是否完全一样,所以一开始卡了好长时间。即使最后想明白了可以建立两个一样的network我也没有生成强度足够强的测试数据,虽然我想不明白十个点的完全图为什么会强度不够(,好在最后经过新的深克隆打赢了十五次的复活赛。最终的数据是十个点的完全图加五十个点的随机图,成功拿下。
第二次作业基本上是在五一之前的一个晚上速通的,基本上没有什么特别的地方。
整个第二次作业最让我困惑的就是我跑室友的评测机总是会很慢,但是当时由于是懒狗,所以只修改了一部分算法,首先我将qts的算法改到了每次增加边与减少边的地方
for (int i : persons.get(id1).getAcquaintance().keySet()) {
if (persons.get(i).isLinked(persons.get(id2)) && i != id2) {
tripleSum++;
}
}
这样子我就可以将算法复杂度减少到o(n^2^)的算法复杂度。
虽然这样确实让我的速度提升了,但是我的强测还是ctle了一个点,主要的问题是出在大量的qtvs当中,当我想像上面的点一样每次对于tag发生变化的时候重新更新一下tag的valueSum,但是实际行动中发现这种方法依旧会导致需要遍历所有的tag,自己计算时觉得这样子反而在tag过多时不好。这个时候,我的大佬室友分享了他的做法让我醍醐灌顶。
原来我的错误的写法是
int sum = 0;
for (MyPerson person : persons.values()) {
for (MyPerson other : persons.values()) {
if (person.isLinked(other)) {
sum += person.queryValue(other);
}
}
}
return sum;
这样子需要遍历tag中的所有包含的人,时间复杂度为o(n^2)
valueSum = 0;
for (MyPerson person : persons.values()) {
for (MyPerson person1 : person.getAcquaintance().values()) {
if (persons.containsKey(person1.getId())) {
valueSum += person.queryValue(person1);
}
}
}
return valueSum;
但是这种写法只需要遍历person有关系的人,对于评测样例这种稀疏图的点时间优化复杂度很大。
有很多人也优化了queryCoupleSum()的算法,但是我并没有进行相关的更改,其实最后也成功过了强测。
本次的junit数据生成完全按照此一次的数据生成,没有了数据的烦恼测bug变快了很多,在这里建议课程组可以统一给出数据生成器或者明确一下数据生成的边界条件,或者是评测机多点错误的信息,比如说本来大概因为什么应该不通过但是最后通过了,同时还可以更加明确其他的评测方式,我们在评测时发现其实很多同学会产生很多样例来测试,这固然无可厚非,但是可能这种测试会将某些测不出来的跳过,或许以后可以增加是因为那一部分不正确的输出,更加全面的输出评测结果。
第三次作业由于是第三次作业,大家对于JML语言的相关了解也提升了一个层次,甚至阅读JML语言跟阅读某些自然语言的定理一样轻松所以课程组直接给了大量的JML代码,仅sendMessage这个方法的JML语言的代码量就有五十行之多,让我的这个方法直接超过了代码风格限制的五十行,只能将他继续优化,拆分。
黑盒测试:功能测试、数据驱动测试,它将被测软件看作一个打不开的黑盒,主要根据功能需求设计测试用例,进行测试。以前常写评测机直接进行测试便是黑盒测试。
白盒测试:也称结构测试或逻辑驱动测试,它是知道产品内部工作过程,可通过测试来检测产品内部动作是否按照规格说明书的规定正常进行,按照程序内部的结构测试程序,检验程序中的每条通路是否都有能按预定要求正确工作,而不顾它的功能。本次作业的junit便是百合测试。
单元测试是完成最小的软件设计单元(模块)的验证,目标是确保模块被正确的编码,使用过程设计描述作为指南,对重要的控制路径进行测试以发现模块内的错误,通常情况下是白盒的,对代码风格和规则、程序设计和结构、业务逻辑等进行静态测试,及早的发现和解决不易显现的错误。
集成测试概念是通过测试发现与模块接口有关的问题。目标是把通过了单元测试的模块拿来,构造一个在设计中所描述的程序结构,应当避免一次性的集成(除非软件规模很小),而采用增量集成。
系统测试概念是根据软件需求规范的要求进行系统测试,确认系统满足需求的要求,系统测试人员相当于用户代言人,在需求分析阶段要确定软件的可测性,保证有效完成系统测试工作。
回归测试概念是当发现并修改缺陷后,或在软件中添加新的功能后,重新测试。用来检查被发现的缺陷是否被改正,并且所做的修改没有引发新的问题。回归测试可以通过人工重新执行测试用例,也可以使用自动化的工具来进行。
这两者缺一不可,前者对于正确性检测与覆盖率有充分的,后者对于程序内部的某些检测有很好的作用。
整个第三单元也没有什么好说的,相比于层次化严谨的第一单元与多线程难debug的第二单元可以说每个周都是博客周的工作量,以后课程组或许可以减少第一单元的难度,或者改变二三单元的顺序,让os学完多线程以后oo在系统地接受java的多线程。