301
社区成员
发帖
与我相关
我的任务
分享本单元由于 JML 的存在,大部分时候做的只是翻译工作,将 JML 的规格描述转换为代码。只是对于少数几个函数,如 queryBlockSum,出于性能考虑,需要进行特殊的结构设计。
本单元实现的社交网络本质上是一个无向带权图。将 MyPerson 作为节点,acquaintance 设计为 HashMap,其中存储了该节点的邻接节点及对应边的权。社交网络的基本功能为增加节点、边,修改权值,查找连通性、连通块个数、三元环个数、最短路径,以及相邻节点间的消息传递等。
本单元代码中,唯一一个不在 JML 规格描述中的类为 Conuter,主要目的在于建立并查集,以提高 queryBlockSum、isCircle 的性能,顺带动态维护了 TripleSum。为了进一步提高性能,在并查集中采用了路径压缩的策略,避免树的深度过大。在删边时,只重建与两个节点有关的部分并查集,可以有效的节省时间。
测试点中会出现查询指令多次重复的情况,此时时间复杂度较高的遍历查找往往会超时,所以动态维护一些需要查询的值就显得很有必要。在 Counter 类中,加边、删边的同时动态维护了 blockSum、tripleSum。对于 queryTagValueSum,在加边、删边、修改权值、addPersonToTag、delPersonFromTag 时,动态维护相关 Tag 的 valueSum。这样的设计虽然增加了简单操作的成本,但同时有效地将 queryTagValueSum 等方法的时间复杂度降低到了 O(1)。
黑盒测试只关注程序的每个功能是否都能正常使用。在测试中,把程序看作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,在程序接口处进行测试,它只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数据而产生正确的输出信息。
白盒测试又称为结构测试或逻辑驱动测试。在测试中,把程序看成一个透明的盒子,测试人员需要理解程序内部的代码逻辑、数据结构和算法,并据此设计测试用例,对程序可能存在的逻辑、性能问题进行检查。
起初采取删边时无脑重建整个并查集的方式,但在数据规模极大时会出现超时现象。为此调整为只重建与两个节点有关的部分并查集,问题得到了修复。此外,也可以设一个脏位,在删边时将脏位置 1,但并不重建,而是在需要查询时再进行重建,这样有效减少了重建的次数,也可以改善性能问题。
在编写此方法时并没有意识到,但如果单纯按照 JML 进行循环遍历,此方法的时间复杂度为 O(n^2)。为此改为了动态维护每个 Tag 的 valueSum。
规格明确了方法的功能,限制了方法的作用范围,但不对其具体实现作要求。在实现规格所要求的功能时,需要考虑到代码的简洁与性能,必要时可以设计规格中未提及的属性、方法、类以辅助实现。
本单元在代码实现上轻松了不少,因为 JML 的要求清晰明确,大部分时候做的只是翻译工作,还挺喜欢这种编程形式的,每个方法的功能很明确,方法间的协同性很好,代码的可读性也得到了提高。在具体实现中,学会了并查集的实现与其优化:路径压缩、按秩合并,以及用动态维护降低方法的复杂度。
三次编程任务结束后,遗憾还是有的,一是对 JML 的阅读不够细致导致 HW11 出现了很愚蠢的错误;二是HW9 的 case6 到底是什么样例,到最后都没过……