OO第三单元总结

梳理JML语言的理论基础、应用工具链情况

  JML是一种行为接口规范语言,可以用于指定Java模块的行为。JML可以让我们对某个JAVA的行为进行规范,而不用真正地陷入到实现的细节中去。

     JML的许多构造配合SMT Slover可以对代码进行形式化验证。

  通过部署JMLUnitNG/JMLUnit可以针对接口自动生成测试用例。

 

按照作业梳理自己的架构设计

  每周写作业的时候都会因为“如果优化架构、可能时间会不够”、“先过了中测交上去再说”等等心理而得过且过,即使知道怎样改也懒得去做。但是每当bug修复和下次作业迭代的时候,就会发现欠的债早晚是要还的,同时也发现其实真正改起来一般都是非常容易的。

第一次作业

MyPath

       不是太重要,直接用ArrayList也不会被卡时间。

       如果想要再优化可以用HashSet来保存互不相同的结点。

  

MyPathContainer

  使用了idPathMap、pathIdMap、nodeMap这三个HashMap,分别表示路径id与路径的对应关系、路径与路径id的对应关系、结点id与该结点出现次数的对应关系。使用HashMap保证了查询的速度。

第二次作业

MyPath

       与上一次相同

MyGraph

       没有建立新类,直接把MyPathContainer类里的方法复制过来,然后补充实现MyGraph接口的方法。

  我使用了一个一维数组nodeId来保存结点id与索引的映射关系、一个一维数组valid来保存对某索引是否建立映射、一个二维数组graph来保存结点之间相连的边数、一个二维数组floyd来保存结点之间的最短路径。

  考虑到对数据量的限制,而且指导书中也提醒了:会改变图结构的指令数很少而查询的指令很多,所以我选择使用floyd算法来计算对时间复杂度影响最大的最短路径问题。在每一次图结构改变时,就计算出整个图中所有结点之间的最短路径长度,并保存在数组floyd中。

第三次作业

MyPath

  与上一次相同

MyRailwaySystem

  没有建立新类,直接把上一次MyGraph类里的方法复制过来,然后补充实现MyRailwaySystem接口的方法。

  其实这次作业中最少票价、最短换乘数、最少不满意度等问题就是一个计算加权图的最短路径问题,我学习使用了wjy大佬的算法来实现。因为上一次使用了floyd算法,对这种方法比较有自信,在这次作业中我同样使用了floyd算法来计算最短路径。我使用了三个二维数组分别保存两个结点之间、在某种权值计算方法下的最短路径,在每次进行图变换的操作后就更新floyd数组和这三个数组。在这里就对如何处理换乘对权值的影响的具体算法不再赘述了。

  

  当然,这种算法的一个缺点就是方便addPath但处理removePath的情况时非常复杂。我目前的水平无法处理这种复杂性,所以我选择在每次removePath时,用保存着的所有Path从零开始、完成一遍生成操作,还是挺浪费时间的。

  另外,对三个图的操作其实完全相同,只是部分参数不同而已,因此可以将这些操作进行封装。想法很美好,现实很残酷,由于我能力不够,只对部分操作实现了封装,大部分还是硬编码了。

  

 

按照作业分析代码实现的bug和修复情况

第一次作业

  因为一方面时间不是很够用,另一方面此前没用过hashmap的我害怕出错,在MyPathContainer中我用了ArrayList直接莽。我没有听劝,我以为炸的不会那样彻底,但是我错了。(还是有收获的,从此以后,在做这单元的作业时不管是要写什么东西,我都会先想一想时间复杂度)而且在进行bug修复时,我发现只要5分钟就可以熟练掌握HashMap……

  最终采用了大部分同学都在使用的使用三个HashMap的方法。

第二次作业

  这次作业出错的主要原因还是没有认真阅读指导书,抱了侥幸心理,低估了数据量的大小。以为“图中可以同时存在的结点个数”指的是在程序执行的全过程中可能出现的结点总个数。因此略微偷懒了一下,

  关于如果remove一条路径后其上的部分结点被完全消除,那么这些结点在图floyd和图graph中如何表示的问题,我的室友建议我在记录结点id与图索引对应关系的数组之外,再开启一个数组记录某索引是否被使用,这样在某结点被消除后,它在图中的原有位置就可以被重新使用。但是因为(因为不想和室友写得太像而被查重)没有认真看指导书中的要求,很傻的使用了一种把已经消除了的结点依旧保存在图和映射关系表中的方法。新出现结点就直接在后面追加。但是图只开了边长max=300的大小。但很显然,在程序运行的全过程中可能会出现的不同结点id数远大与300,果不其然的炸掉了。

  

  (与nodeMap的使用方法有点相似,每次add路径时若加上了一条边,就对这条边保存的值累加,若减一条边则减一,delete的时候若结点被完全消除,graph里所存的值自然地减到了0。当时的我是这么想的……)

  解决的方法很简单,除保存映射关系的数组nodeId外,再建立一个保存是否该索引已被使用形成映射的数组nodeIdI

  除此之外,我还犯了一个非常低级的错误:isConnected函数中未判断结点是否存在就直接判断是否相同并返回。

  

第三次作业

  助教在群里反复说wa的概率远大于tle,但是我们并没有重视,没有去认真检查自己代码的正确性,而是一直在担心这种算法会不会有tle的问题。强测的结果显示这种算法远远不会tle,但是我的代码在计算连通块时爆栈了……

阐述对规格撰写和理解上的心得体会

  也许也有我对JML和面向对象的思想理解不够的原因。我个人认为JML的重要作用就是能够准确的描述一种行为,而不具有自然语言可能发生的二义性问题,除此之外我并没有什么更深刻的体会。老师在课上不断提到一定要先设计后编码、先设计后编码,先设计后编码,设计比具体实现更重要。

  在第一次实现Path接口时,我也第一次体会到了这种设计方法的优势。

  而在第三次规格作业中,通过JML语言描述了最少票价、最少换乘数、最少不满意度、连接块等定义,我们来负责它的具体实现。但是很显然这个具体实现非常复杂,需要大量的算法设计,而设计就意味着定义行为。针对这种复杂算法问题,我们很难在设计的初始阶段就定义好所需要的大部分行为(特别是一些private方法)。当然,定义一些明显有必要的方法是可以做到的,比如这次的MyRailwaySystem接口中就定义了containsPathSequence、isConnectedInPathSequence、getTicketPrice、getUnpleasantValue这些方法,虽然没有要求我们来实现这些方法,但可以想到,这些方法是可以作为达到目的的中间方法来使用的。尽管在我采用的算法中应该应用不到这些方法。总而言之,就我不成熟的理解而言,我感觉结构的设计是必须涉及算法设计的。

 

posted @ 2019-05-22 20:29  syncline  阅读(174)  评论(0编辑  收藏  举报