OO第三单元回顾总结
作者:互联网
前言
本单元围绕JML进行规格化设计,契约式编程的思想基本上贯穿了整个单元。
本单元最大的新体验在于:作为大工程中某一部分代码的实现者,履行应尽的责任,而非像前几个单元一样自行设计完整架构并完成所有代码。更直观的感受在于:由于课程组提供了细化到每一个类和类方法的JML描述,完成作业更像是做一道道小题,颇有一种“闯关”的快感,写起代码来体验极佳!
尽管如此,本单元依然存在一些难点,也存在许多值得探讨的课题,接下来将详细描述。
一、架构设计
本单元主要需要设计Network
中的Gruop
、Person
实例的生成、关系建立和查询等方法。其中涉及一些图论知识。
1. 第一次作业
第一次作业的难点在于完成query_circle
和query_block_sum
两种查询。在实现中,本人使用了并查集算法。
具体而言:结点的连通关系是一种等价关系,具有自反性、对称性和传递性,图中的所有结点可以被分成多个连通分量(参考离散数学知识)。而本单元Person
实例间的连通关系是同样的道理,因此可以根据是否连通,将Person
划入到多个集合中,互相连通的Person
在同一个集合。
在代码实现上,集合的概念通过并查集算法实现,位于同一个集合的Person
实例形成一个树状结构,则在查询时,可:
- 通过判断两个
Person
所在树状结构的根结点是否相同,判断两个Person
是否连通,完成query_circle
查询 - 通过查看有多少个不同的根结点,完成
query_block_sum
查询
2. 第二次作业
第二次作业的难点在于完成query_least_connection
查询,需要设计最小生成树的算法。在实现中,本人使用了Kruskal算法。
具体而言:Kruskal算法不断从未选中的边集中选择权重最小的边,尝试将此边加入已选中的边集,并判断引入此边是否会导致环的出现。实现的难点就在于判断是否出现了环。在本人的具体实现中,此处也使用了并查集算法,当两个结点依靠已选中的边可连通时,两个结点位于同一个集合。由此,只有当前边连接的两个结点不在同一个集合时,此边才能成功加入。以下图为例:
假如当前结点连接如左图所示,且Kruskal将继续尝试将Edge1和Edge2加入边集:在判断Edge1时:由于结点2和3已经位于同一个集合,因此无法继续加入;而在判断Edge2时:结点5和7位于两个不同的集合,因此可加入此边。
3. 第三次作业
第三次作业的难点在于完成send_indirect_message
查询,需要完成最短路径算法。在实现中,本人使用了堆优化的Dijkstra算法。
具体而言:Dijkstra算法从当前未到达的结点集合中,选取最近的结点标记为到达,并用此节点更新其他未到达结点的距离。由于需要不断更新结点的当前距离,并涉及选取最近的结点,因此类似于优先队列问题,可以使用小顶堆进行优化。在本人的具体实现中,使用了Java自带的PriorityQueue
容器完成。
二、测试数据准备
本单元需要利用JML规格来准备测试数据。总结而言,满足各个类方法前置条件的测试数据均应该覆盖。而本单元真正具有前置条件的类方法非常少,大部分都通过抛出异常解决,因此,测试数据就几乎涵盖了所有可能的情况。
具体来说,本人在构造测试数据时,主要分别构造正常数据和异常数据,并分别对每个异常都构造数据。以addToGruop
方法为例:
addToGroup
方法设计到三种异常。因此,在构造测试数据时,需要包含:
- 正常的测试数据:存在
id
为id1
的Person
,存在id
为id2
的Group
,且Person
目前不在Group
中 - 涉及
GroupIdNotFoundException
的测试数据:不存在id
为id2
的Group
- 涉及
PersonIdNotFoundException
的测试数据:不存在id
为id1
的Person
- 涉及
EqualPersonIdException
的测试数据:id
为id1
的Person
已经位于id
为id2
的Group
中
三、性能分析
本单元严格限制了代码运行时间,以下总结了可能出现性能问题的指令,与本人的解决方案。
1. query_circle与query_block_sum
除了上文中提到的,这两条指令使用并查集完成外。在实现过程中,还需要进行路径压缩。具体而言,路径压缩通过重构并查集的树状结构,减少查询根结点时间。以下图为例:
在查询到结点3的根结点为结点1之后,使结点3直接指向结点1,由此可以减少下次查找结点3根结点时花费的时间。
2. query_group_value_sum
如果在每次收到qgvs
指令后,都通过二重循环,遍历Group中两两结点的关系,则时间复杂度为O(n^2),无法通过本单元测试。解决方案是,在Group中维护一个变量valueSum
保存此查询的答案。valueSum
需要在遇到以下指令时检查是否需要更新:
- add_relation:需要检查两个
Person
是否都位于同一个Group
,如果是,则需要更新valueSum
- add_to_group:需要检查加入的
Person
与目前位于Group
中的每一个Person
是否有关系,如果是,则需要更新valueSum
- del_from_group:需要检查删除的
Person
与目前位于Group
中的其他每一个Person
是否有关系,如果是,则需要更新ValueSum
3. query_least_connection
如上文所述,利用依赖并查集的Kruskal算法,可以满足时间限制。
4. send_indirect_message
如上文所述,利用堆优化的Dijkstra算法,可以满足时间限制。
四、Network拓展
本人思路如下:构建Advertiser
、Producer
、Customer
三个接口,这三个接口均继承Person
接口。并构建Product
类,封装产品信息。
Advertiser
接口见下。Advertise
维护productInfo
数组,通过addAdvertise()
方法接受来自Producer
的产品信息,通过getAdvertise()
方法提供产品信息。
Producer
接口见下。Producer
拥有一个Product
实例,封装自身产品信息,同时Producer
拥有一个Message
变量,存放买家信息。Producer
通过useAdvertise()
方法向Advertiser
提供自身产品信息,通过receiveBuyInfo()
方法获取买家信息。
Customer
接口见下。Customer
通过buy()
方法向Advertiser
表明购买意愿。由于buy()
方法设计消费者的具体偏好,因此此处并未给出具体的JML规格。
五、学习体会
总结而言,本单元学习了契约式编程的重要思想,并通过三次单元作业,掌握了读写JML规格语言的能力,能根据JML规格语言实现代码。就笔者而言,这是头一次涉及到多人合作完成代码的方法和思想,有非常独特的体验。
在完成单元作业方面,本单元确实难度较低,学习过程非常轻松愉快。同时,本单元间接帮助笔者回忆了离散数学和数据结构的一些相关知识。也只有在这种情况下,笔者才能意识到自己对于过去学过的知识遗忘之快,可见过去学习并不那么扎实。
最后,希望能顺利完成第四单元,继续加油!
标签:OO,结点,Group,测试数据,Person,回顾总结,query,单元 来源: https://www.cnblogs.com/syjzdkfc/p/16344802.html