OO第三单元小结

一、JML语言基础

 

二、JML自动测试样例

三、架构设计

  1.JML第一次作业

  第一次作业相对而言比较简单,唯一需要多考虑的就是计算不同点的个数。

  除去统计不同节点的个数之外,其他所有的方法都可以通过两个容器十分简单的实现,即通过id查找path(一个list即可)和通过path查找id(一个map)。关于计算不同点的个数,笔者采用的是增加一个容器,用来统计每个点被不同路径引用的次数。这样在统计节点个数的时候只需要查看这个容器的size即可。

  有关容器的选择:用id查找path的话,由于id是从1开始连续递增的,且查找的次数会远远大于增删的次数,所以我选择了最基本的静态数组。用path查找id,在Path类中已经重写好了hashCode,而且path查找id和添加进去的顺序是毫无关联的,所以选择了HashMap而不是TreeMap。统计每个节点的引用次数,由于节点的id是离散的,所以采用了HashMap<节点id,引用次数>来存储。

  如此设计之后,一般的查询方法复杂度都是O(1),因为都是官方容器类的一些最基本的get(),size()方法。

  2.JML第二次作业

  第二次作业相比较第一次作业而言,增加了计算最短路径、两个点是否连接和两个点之间这三个需求。但是其实如果计算了最短路径,后面两个问题也就变成了是否存在最短路径,和两个点之间的最短距离是否为1(一个点到自身的情况要单独考虑,这也是我当时的一个bug)。

  第一次作业中已经存在的设计完全不需要改变,由点映射到引用次数的HashMap刚好也可以应用于本次新增加的需求containsNode。在计算最短路径方面,我选择了Floyd算法而并没有选择单源最短路径分别计算作为计算的方法。单源最短路径的计算方式其实可以更好地分摊计算的时间。如果是采用Floyd算法,每当添加一条路径,或是删除一条路径,都需要把整个图重新计算一遍(把新添加的路径中的结点分别作为周转点计算整个图,并把新添加的路径中的节点作为起点或终点计算),这样的好处是只许要计算一次,但是坏处是每次计算时间都非常的久。而如果是单源最短路径,可以在访问到该点的时候判断这个点的情况需不需要更新(有无新路径添加),每次只需要计算需要计算的部分,相较于多源算法,节约了一些不必要的计算。多源的算法相比较而言则是省心,每次添加或删除后一步到位,不需要关心之后是什么操作。

  由于原生的Floyd思想是遍历所有点,把每个点作为中间节点来更新其他所有点之间的距离。这就需要存储两个点之间的距离。最基本的算法是静态数组的矩阵存储,访问速度快,思路清晰,代码简洁。但是矩阵的坏处也有,首先容量定死,不易于维护。其次就是当更新图的时候,可能会需要矩阵的调整,即删除点之后,要把所有索引在改点之后的点索引减一,这种频繁地移动是较为浪费时间的。如果在删除点之后不改变其他点的索引,则需要记录矩阵索引的有效性,而添加和删除的时候根据该记录执行,比较容易出错,而且代码写出来可读性很差,较为面向过程。所以我选择了另外一种矩阵的存储方式。新建了NodePair类,用来表示两个点,即将矩阵的两个索引作为起点和终点存储进去。再利用HashMap将这两个点映射到他们之间的距离。效率而言,肯定是会比静态数组慢的,但是从可维护性和代码的可读性上,有了明显的改进。

  3.JML第三次作业

  第三次作业对比第二次作业相当于添加了另外三个“最短路径”算法。这次作业比较棘手的就是换乘的问题。如果能有效地处理掉换乘的问题,那么这四种算法其实都是第二次作业的复制。

  关于换乘的处理方法主要有二,一种是在每条路径上默认添加一次换乘,这样算到最后只需要减掉一次多余的即可。另外一种则是将换乘结点拆为两个点来处理。

  在架构的选择上,这次的作业所求的四种“最短路径”算法基本相同,不同的是两站之间的距离和换乘的代价。因此我采用的方法是新建一个对象作为地图来让这四种算法共享,每次增删路径他们的消息共享。而这四种算法分别对应四个子图,每个子图有自己的初值和算法。一开始的考虑是让他们四个共用一个计算最短路径的方法,只需要在调用该方法的时候传入不同的代价即可。但是最底部满意度和另外三个算法略微有些不同,所以还是分开建立了四个子图,并为每个子图写了自己的算法。四个子图实现了一个SubGraph接口,里面有访问和求最短路径的方法。(事实证明想的很好却根本没必要,因为不存在在调用时不知道这是哪个子图的情况)这样分开写的好处就是易于维护,如果对哪个算法需要单独做更改,比较简单,只需要在相应子图里做修改即可。但是缺点就是代码重复度较高,在没有特殊需求时,算法基本相同。

  在每个子图内部,有他们所共享的当前地图情况,和计算出的最短路径矩阵,共享当前地图情况比较方便。但是不好的是因为类似前两次作业的所有容器为了方便四个子图使用都建立在了共享的子图中,这样的话子图的访问的确便捷了,但是所有在MyRailwaySystem里面的方法基本上都需要调用共享地图中相同的方法,显得很没有必要。

  最大连通块的问题采用并查集解决。一个HashMap即可解决。 

四、bug及修复情况  

  1.JML第一次作业

  第一次作业比较简单,在写的时候和测试的时候都没有发现bug。

  2.JML第二次作业

  第二次作业产生了两个比较严重的bug。

  第一个bug是由于对JML的疏忽。当计算两个点之间是否有边直接相连是,判断了他们的最短路径是否为1。但是如果两个点相同,且存在边时,他们的最短路径将会是0。在JML中是明确地写了的,但是由于想当然了,就产生了这个bug。

  第二个bug是对操作的考虑不周。当自行建立矩阵的时候,需要建立点的id到矩阵索引之间的映射。这个映射我希望使用一种容器类,而不是静态数组。在选择LinkedList和ArrayList时,我想的是如果选择ArrayList,那么增删点的时候就会产生大量的前后移动的情况,很浪费时间,于是就选择了LinkedList。忽略了在计算的时候需要大量地通过索引来访问点,这样在强测中有两个点就超时了。改为了ArrayList之后顺利解决。

  3.JML第三次作业

  第三次作业中产生的bug很多,比较严重的是在调用最短路径的时候考虑不周全,导致会抛出异常。具体一点来说就是在访问存在边的时候没有考虑点不存在的情况,类似第二次作业中的bug。

  另外一个导致很多店tle的原因是没有加入缓存机制。我在本次作业中使用的依然是Floyd,但是每条路径根据Floyd不拆点算完之后是可以缓存下来的,在下次计算的时候不需要再次计算,只需要加入即可。在添加了缓存机制后就不tle了。

五、心得体会

  根据JML来实现具体的功能感觉还是比较接近未来的实际工程一些吧。无论是在编写JML时思维逻辑的严密性,还是在根据JML实现具体代码时对需求的理解,都是有百益而无一害的。个人感觉写JML应该是要比根据JML来实现具体功能更加体现一个人的设计。当然,根据JML来实现也很考验一个人的代码基本功和对架构的设计。

posted @ 2019-05-22 19:12  ericliu0206  阅读(115)  评论(0编辑  收藏  举报