0x585350

BUAA面向对象课程博客 第3弹:简单JML规格实现

1 概述

本单元我使用Java基于JML规格实现了一个社交网络模拟系统。本文由以下两部分内容组成:

  • JML规格的解读与书写思路
  • 作业中图相关算法的实现

2 JML规格化设计

JML语言用于对Java类的方法与属性进行形式化的描述,以表示对其行为的预期,使得设计规格化的程序成为可能。

2.1 JML规格的解读

JML语言形式化程度高,但易读性不一定好,按照自然语言阅读顺序逐行阅读是不科学的。以方法的JML描述为例,应该如下层次化阅读:

  1. 确定方法behavior数量以及各behavier执行条件,完成控制流的编写;
  2. 针对每个behavier,确定其返回值和可以修改的属性,依次完成计算;

2.2 JML规格的书写

同样地,JML的编写也应当按照类似的层次化方法。以方法的JML描述为例:

  1. 确定分支个数,确定behavier数量与进入条件;
  2. 对于每种behavier,确定修改的属性与返回值;

3 关键算法

我自建Graph<T>泛型类封装了图相关算法。其中还辅助设计了节点、边、并查集等类。由于使用了泛型,该类可复用程度很高。

3.1 第一次作业

本次作业关键算法是查询网络中两个节点的连通性。针对作业所实现的可增加边与节点的动态图,我最终使用了查集算法。
并查集的核心思想是将图的连通分量的所有节点都映射到该连通分量的同一个节点,即使用该节点来代表其所在连通分量这个”等价类“。
依托于树数据结构,将连通分量中所有节点构建成一棵树,并用树的根节点代表这个连通分量即可。在合并两个连通分量时,只需将两棵树合并。
需要注意的一点是在树的深度由于合并逐渐变大时,查询效率会降低,因此需要使用路径压缩或按秩合并等方法进行优化。我使用了一种基于用户使用进行优化的策略:在用户每次查询时,将查询路径上所有点压缩到根节点上。如此可以根据用户使用,以最小代价完成路径压缩。

3.2 第二次作业

本次作业的关键算法在第一次次作业的基础上增加了图中某节点所在连通分量的最小生成树的查询。我使用了Kruskal算法。
首先利用并查集查询该节点的连通分量中的所有节点,并获得该连通分量中所有边,然后开始使用Kruskal算法。
对于连通无向图$G=<V,E>$,V是节点集合,E是边集合,寻找其最小生成树的Kruskal算法描述如下:

  1. 使用S[]表示最小生成树的边集;
  2. 初始化S={}为空;
  3. 更新:从E中移出权值最小的边e,若S加入e后成环,则丢弃e,否则将e加入S;
  4. 结束条件:若n(S)=n(V)-1,结束,否则跳转至4。

3.3 第三次作业

本次作业的关键算法在第二次作业的基础上增加了图中两节点最短路径长度的查询。我使用了Dijsktra算法进行实现。
首先需要利用并查集查询这两节点是否连通,如果连通,则获得连通分量所有节点,然后开始使用Djsktra
对于连通无向图$G=<V,E>$,V是节点集合,E是边集合,寻找$v_0$到$v_1$的最短路径Dijsktra算法描述如下:

  1. 使用dist[]数组维护$v_0$到图中其它节点的距离,t[]数组表示对应距离是否是最短路径;
  2. 初始化:设$v_0$对应距离为0,其余节点距离为无穷(-1);t[]全初始化为false;
  3. 更新:找出t[i]为false的节点$v_i$中dist[i]最小者,设t[i]为true,更新所有与$v_i$邻接的节点$v_j$的距离dist[j]=min{dist[j], dist[i]+d(i,j)},其中d(i,j)是i,j间边的权重;
  4. 结束条件:若t[1]为true,返回dist[j],否则跳转至3。

3.4 未来扩展

针对未来加入AdvertiserProducerCustomer等Person子类的情况,我采取以下方式进行扩展。

  1. 添加AdvertiseMessage, ProductMessageBuyMessageMessage子类;
  2. Advertiser持续向邻接节点发送AdvertiseMessage;
  3. Customer每接收到AdvertiserMessage,向Advertiser发送BuyMessage;
  4. Advertiser接收到BuyMessage,转发给Producer
  5. Producer接收到AdvertiserMessage,回复ProductMessage
  6. Advertiser收到ProducerMessage,转发给Customer;
  7. Customer保存接收到的ProductMessage

4 BUG测试与修复

4.1 黑箱测试

作业一使用JUnit测试时发现,由于程序逻辑较为简单,测试代码基本上是把原来的程序再写了一遍。所以对JUnit仅做了解,仍然使用自动黑箱测试,通过生成测试样例,分享对拍进行测试。

4.2 BUG修复

本次作业一共出现了两个bug:

  1. 对于空网络的平均值计算错误:发生了除以0;
  2. Dijisktra算法时间过高:通过使用优先队列优化完成;

5 心得体会

本次作业中我学习到了基于JML的规格化程序设计。JML通过规格化描述,将程序架构设计与算法具体实现分离开来,使得本次作业的开发时间大幅减少。架构设计者值只需要关注每个类、属性方法要满足的需求,而开发者则将所有精力投入到算法的实现上。

posted on 2022-06-06 13:38  ^^^TOO_LOW^^^  阅读(46)  评论(0编辑  收藏  举报

导航