OO第三单元总结
OO第三单元总结
如何自测
关于如何利用JML规格来准备测试数据这一问题:
- 首先一开始的时候,尝试使用课程组介绍的工具
Junit
。但后来发现,该工具的作用主要是能够更佳细致地测试每一个函数的功能正确性。但正确答案、以及数据仍需自己给出。 - 之后便是根据JML准备测试数据。这一部分,首先是随机测试随机测试的覆盖性较弱,针对性也较弱。但能初步地验证程序的正确性,验证不同模块的功能性正确。之后便是针对测试,这一部分主要是集中在两块,一是函数能否正确抛出异常,二是根据JML描述中十分耗时的部分,构造相应的阴间数据,来测试程序的算法是否会出现CPU超时的情况。这主要是通过构造复杂的图结构,增加查询语句。实现诸如稀疏图,完全图,等等完成。这其中,第二部分的测试用例十分有效。也让我更深刻地理解了不同
JAVA Container
之间地性能差异。为了优化程序,不断地改进表达的数据结构,从最开始的600s到最后提交时的2-3s。构造这部分测试用例十分有效!。
图模型构建和维护策略
本次模型涉及到的图结构主要为类Person
与类Person
之间,并带有加权边value
。对此,我们在MyPerson
类内部,用HashMap
来维护顶点与边的关系。用id
作为键值,索引得到相应的边权值。
而对于该图结构,需要处理的三类问题分别是最小生成树、最短路径和并查集。由于这两个问题,在迭代维护上可以采用不同的策略,于是我采用了不同的数据结构来维护这两个算法。
- 最小生成树:针对最小生成树,其具有强烈的迭代特性。我们很容易得出,在得到当前图的最小生成树时,若新增一条边,则对应以下两种情况。
- 新增边在最小生成树内部:这种情况时,由于原最小生成树边集的数量为n-1。因此根据定理,新增一条边后,只需要去除一条边使其不成环,便得到新的生成树。而为了得到最小生成树,需要保证留下最小边。因此,我们构建了一个有序递增边集。对其仅需遍历,判断是否成环操作,来去除第一个使其成环的边,便可以得到新的最小生成树。
- 新增边使其连通了其他图或顶点:对于这种情况,我们仅需要维护有序递增边集,依顺序插入即可,不需要去除额外的边。
由上可知由于这个有序递增边集存在大量插入,删除操作。因此我们选用链表来实现该最小生成树算法。
- 最短路径:针对最短路径算法,我选用迪杰克斯拉算法。该算法的好处是实现容易,思考量低。但同样在整个算法的实现过程中我们需要维护两个集合,已加入路径顶点,未加入路径顶点;以及一个距离矩阵,原点到未加入路径顶点的距离。为了实现不重的特性,采用
HashSet
作为存储顶点的容器,而对于路径矩阵选用HashMap
实现。这样便能较为优雅的实现迪杰克斯拉算法。 - 并查集:并查集主要是判断任意两点之间是否连通这一问题。对于该问题,我首先想到的是能否使用
HashSet
来表示处于同一个连通子集的个体。但如何来保存所有的连通子集呢。如果用List
则对于每一次查询需要遍历List
来判断自己处于哪一个连通子集中。因此,采用HashMap
,Key为MyPerson
类自己,Value为相对应的连通子集。因此查询就变味了O(1)。
性能问题和修复情况
这个在每一次作业都有遇到。
第九次作业:主要是针对并查集的优化问题,原始采用ArrayList<HashSet<Person>>
的嵌套方式,储存该图下的每一个连通子集。此时对于每一个查询是否处于同一连通子集操作的时间复杂度为O(n)。而后优化为HashMap<Person, HashSet<Person>>
,此时在针对于每一个查询,时间复杂度优化到O(1)。
第十次作业:这一次作业是优化最多的部分。由一开始的对一份数据从600s优化为2-3s。
- 算法优化,最开始为Prim算法。这需要我们遍历每一个点的每一条边,这是一个十分耗时间的操作,因为我们储存边、顶点的结构体是
HashMap
而HashMap
的遍历是一个十分费时的操作。所以改为Kruskal算法。 - 数据结构优化,一开始未能注意到最小生成树的迭代特性。在每一次访问最小生成树时,遍历
HashMap
容器重新计算。导致每一次时间开销较大。而后,转为如上文所说。用自定义的链表结构实现,同时仅保存最小生成树的边,设置脏位,防止频繁计算。极大地优化了性能。 - 实现细节优化,这一步主要针对算法编写过程中的遍历行为。对于kruskal算法用到的许多集合,通过减少遍历次数,减少容器使用量等等。都可以进一步优化速度。
第十一次作业:这一次作业主要是针对迪杰克斯拉算法。该部分的优化遇到的问题仍然是上面所提到的HashMap
遍历问题。最大的问题是距离矩阵,由于未维护最小堆。在已加入顶点越来越多时,距离矩阵的键值对数量也越来越多。而我们需要遍历得到未加入顶点的最小值,所以需要遍历的对象也越来越多,这非常耗时。于是做了一个优化是,在每次加入顶点后,将该顶点从距离矩阵中删除,这样便可以得到更小的距离矩阵。从而加速。
JML规格
Advertiser
/*@ public normal_behavior
@ requires containsProduct(product.getId()) && contains(advertiser.getId())
@ assignable people[*].favorProducts;
@ ensure ((\forall int i; 0 <= i && i < people.length && people[i] instanceof Customer && people[i].isLinked(advertiser)
@ && !people[i].hashProduct(product.getId);
@ people[i].favorProducts.length == \old(people[i].favorProducts) + 1 &&
@ (\exist int j; 0 <= j && j < people[i].favorProducts.length; people[i].favorProducts[j] == product)
@ ensure ((\forall int i; 0 <= i && i < people.length && people[i] instanceof Customer && people[i].isLinked(advertiser)
@ && !people[i].hashProduct(product.getId);
@ people[i].favorProductsValue.length == \old(people[i].favorProductsValue) + 1 &&
@ (\exist int j; 0 <= j && j < people[i].favorProductsValue.length && people[i].favorProducts[j] == product;
@ people[i].favorProductsValue[j] == 1)
@ ensure ((\forall int i; 0 <= i && i < people.length && people[i] instanceof Customer && people[i].isLinked(advertiser)
@ && people[i].hashProduct(product.getId);
@ (\exist int j; 0 <= j && j < people[i].favorProductsValue.length && people[i].favorProducts[j] == product;
@ people[i].favorProductsValue[j] == \old(people[i].favorProductsValue[j]) + 1)
@ also
@ public exceptional_behavior
@ signals (ProductIdNotFoundException e) !containsProduct(product.getId())
*/
public void publishAdvertise(Product product, Advertiser advertiser);
Producer
/*@ public norm_behavior
@ requires !containsProduct(product.getId())
@ assignable products;
@ ensure products.length = \old(products.length) + 1;
@ ensure (\forall int i; 0 <= i && i < \old(products.length);
@ (\exists int j; 0 <= j && j < products.length; products[j] == (\old(products[i]))));
@ ensure (\exists int i; 0 <= i && i < products.length; products[i] == product);
@ also
@ signals (EqualProductIdException e) (\exists int i; 0 <= i && i < products.length;
@ products[i].equals(product));
*/
public void produceProduct(Product product);
Customer
/*@ public norm_behavior
@ requires contains(id) && containsCustomer(id);
@ assignable people[*].saleProducts;
@ ensure \result == (\max int i; 0 <= i && i < getPerson(id).favorProducts.length; getPerson(id).favorProductsValue[i])
@ also
@ signals (PersonIdNotFoundException e) !contains(id);
@ signals (CustomerIdNotFoundException e) contains(id) && !containsCustomer(id);
*/
public Product buyProduct(int id);
本单元学习体会
本单元的学习心得主要是针对JML规格的一系列问题。对于JML规格本身而言,他是一个很好地能够使得机器以及人类都能理解规格语言,缺点在于表述不够直观,针对一些复杂的需求,往往会占用大量篇幅。容易让人忽视细节。我这次的作业中便出现了这样的问题,在第十一单元的作业中,忽视了一个异常处理。导致强测诸多测试点出现问题。这是十分不妙的。
于此,我认为本单元主要需要我们养成规格化的阅读能力,能够解析复杂的JML格式。理解不同类之间的关系,以及JML语法的应用等等。这对于以后我们在其他场合,建立相应的规格描述思想,规范十分有帮助。