JML是對java代碼進行規格抽象的一種表達手段。java
面向對象的重要原則就是過程性的思考應該儘量地推遲。而JML能夠幫助咱們去靠近這個原則。其經過一些邏輯符號等表示一個方法是幹什麼的,卻並不關心它的實現,幫助你更好的用面向對象的思想去實現代碼。node
使用JML編譯器 能夠編譯含有JML標記的代碼,所生成的類文件會檢查JML規範。git
若是程序沒有遵循規範,則會拋出unchecked exception來講明程序違背哪一條規範。github
JML工具備JML運行期斷言檢查編譯器、Jmldoc、jmlunnit等,其中jmlunit能夠生成一個java類文件測試的框架,能夠方便得使用Junit工具測試含有JML標記的java代碼。算法
由於windows下用命令行真是太不方便了,我直接在IDEA中部署了openJML,官方github下載openjml,解 壓,而後導入IDEAwindows
Check-->Run Check便可運行數組
以MyPath爲例:數據結構
/*D:\Mine\hw10\src\MyPath.java:12: 警告: The prover cannot establish an assertion (PossiblyNegativeIndex) in method MyPath nodes.add(nodeList[i]);*/
public MyPath(int... nodeList) { for (int i = 0; i < nodeList.length; i++) { nodes.add(nodeList[i]); //error!
/* 這是典型的下標可能爲負的錯誤 * 須要增長 i >= 0 的判斷 */ nodesset.add(nodeList[i]); } } /*D:\Mine\hw10\src\MyPath.java:49: 警告: The prover cannot establish an assertion (ExceptionalPostcondition: D:\Mine\openjml\openjml.jar(specs/java/lang/Object.jml):76: 注: ) in method equals if (nodes.size() == ((Path) obj).size()) {*/
if (nodes.size() == ((Path) obj).size()) { //error!
/* 這是一種無異常判斷的錯誤 * 這裏須要增長異常判斷 */
return true; } else { return false; }/*D:\Mine\hw10\src\MyGraph.java:15: 警告: The prover cannot establish an assertion (NullField) in method MyGraph
以MyGraph爲例
/*D:\Mine\hw10\src\MyGraph.java:15: 警告: The prover cannot establish an assertion (NullField) in method MyGraph private HashMap<Integer, Path> idToPath = new HashMap<>();*/
private HashMap<Integer, Path> idToPath = new HashMap<>(); //new HashMap<>()中<>補全 /*D:\Mine\hw10\src\MyGraph.java:301: 警告: The prover cannot establish an assertion (ArithmeticOperationRange) in method addEdge: overflow in int sum count++;*/ count++; //加法溢出 /*D:\Mine\hw10\src\MyGraph.java:91: 警告: The prover cannot establish an assertion (ExceptionalPostcondition: D:\Mine\hw10\src\com\oocourse\specs2\models\PathContainer.java:57: 注: ) in method addPath if (path == null || !path.isValid()) {*/
if (path == null || !path.isValid()) { //error //增長異常處理
return 0; }
一樣,我沒有成功嘗試命令行,仍然選擇在IDEA中導入jmluniting.jar架構
(1) 在IDEA中下載TestMe框架
(2) project structure-->modules-->導入jmluniting.jar
(3) 對應類上alt+Ins --> test --> testme --> testNG
public class test { private int a; private int b; public test(int a1, int b1) { a = a1; b = b1; } public int add() { return a + b; } public int sub() { return a - b; } public int mul() { return a * b; } }
生成測試代碼
import org.testng.Assert; import org.testng.annotations.Test; public class testTest { test test = new test(0, 0); @Test public void testAdd() { int result = test.add(); Assert.assertEquals( result, 0); } @Test public void testSub() { int result = test.sub(); Assert.assertEquals(result, 0); } @Test public void testMul() { int result = test.mul(); Assert.assertEquals(result, 0); } } //Generated with love by TestMe :) Please report issues and submit feature requests at: http://weirddev.com/forum#!/testme
運行
主要是設計Path和 PathContainer
對於Path,觀察JML發現:
一、Path中儲存了可重複的nodes,所以能夠採用可裝入可重複元素容器:數組、arraylist等。
二、Path具備統計不一樣nodes的功能,能夠考慮使用HashSet來進行存儲。
對於PathContainer,觀察JML發現:
一、該類所操做的主要數據是id-->Path 的映射,且是一種雙射。基於映射的特性,能夠採用HashMap來存儲。
二、對於雙射:id-->Path,要求有雙向查找功能,即須要實現函數和反函數的兩種查找功能。又由於其是雙射,兩個HashMap來進行存儲無疑是可行且高效的。
三、HashMap的key須要注意其compareTo接口,對於Path,須要咱們重寫hashCode()。
注意:重寫hashCode()要注重效率,毫不能採用直接return 1,必定要有所區分,儘量區別。
四、與Path相似,這裏一樣須要統計不一樣Path的個數。簡單的遍歷必然會形成TLE。能夠採用<path, count>的形式儲存,直接統計其size()便可。把複雜度交給add和remove。
主要是實現Graph。
對於圖,無疑須要考慮兩點:
一、數據結構
圖須要考慮的是邊和點。
對於點,
能夠直接沿用上一次的存儲方式。
對於邊,
(1)大機率是個稀疏無向圖,採用鄰接矩陣不合適。
(2)回憶DS課關於稀疏矩陣的存儲方式——鄰接表。
(3)如何存鄰接表?咱們須要儘量選擇一種好的存儲形式和容易來幫助咱們快速操做邊(這裏包括已知from,搜索to、便於增、刪、查.....)
(4)基於(3)能夠考慮構造映射起點-->全部終點+出現次數(便於刪除)。
(5)基於(4),HashMap來作映射再好不過了。
二、圖算法
主要涉及兩種算法和一種機制
(1)圖的連通性算法
不管是dfs、仍是bfs、仍是並查集,我的感受複雜度的差距不大。可是我更喜歡並查集+優化路徑壓縮,由於並查集維護起來我的感受比較方便,查找也比較快捷,緣由在於
**並查集是若干個集合(連通塊),可以實現較快的合併和判斷元素所在集合的操做。(聽起來就很是適用本次的狀況??)
**查找複雜度O(1),合併複雜度o(h)(h爲合併後樹的高度)
**union的複雜度能夠分擔給addPath
並查集學習連接http://www.javashuo.com/article/p-yytvowpn-hz.html
(2)最短路徑算法
Dijsktra? or Flord?
我選擇Dij,緣由在於Flord在個人認知裏是有點浪費的。每次都求出全部點,最短路徑,萬一用不到呢?萬一又被add、remove了呢?可是大量數據+少許圖結構修改的前提下,選擇Flord好像也不錯。
(3)Cache機制
其實很簡單,就是把已經算過的不管是最短路徑仍是連通性,存儲已經計算好的結果。(滾雪球)
下次調用時,先查詢cache,後計算,防止重複計算。
修改圖結構,須要清空cache。
這是一次讓人頭疼的做業。
與前面大有不一樣的就是相似最短路徑的算法增長了換乘的消耗。也就是weight(1-->1) == 0 || weight(1-->1) != 0
拆點是種好方法,但點太多容易炸內存,也容易炸時間。
所以,選擇合適的拆點方法是重中之重。
直接強拆:
點抽象成<node, pathId>。增長node1 == node2 && pathId1 != pathId2 的邊的權值
巧妙拆:
來自討論區的想法。每一個點拆成站臺+起點+終點
我的實現了這兩種拆點方法。
先寫了強拆的方法,發現1000+指令就有點受不了??不知道是否是cache機制作的不夠好?
後來臨時補了巧妙拆的方法,時間上有了提高,但最後提交截至前出現bug沒有解決,無奈交了初版。
最後果真被炸的體無完膚。
第一次做業:
這是一次比較簡單的做業,可是因爲我考慮不周,出現了TLE的問題。
主要問題在於
(1)hashCode()重寫過於簡單,直接return1
(2)沒有雙HashMap配合,致使搜索複雜度太高
第二次做業:
本次做業沒有發現bug
第三次做業:
本次做業因爲巧妙拆點的方法在最後出現了bug,沒有完成修復,最後提交了初版強拆點的,致使了TLE
面向對象的思想要求咱們關注和設計每一個類和方法實現的效果,推遲中間過程化的思考。規則撰寫很是有利於咱們去遵循這個原則,JML幫助咱們在設計層面更加規範化得思考每一個類和方法的狀態,表達邏輯嚴謹,又能給後續編碼帶來好的指導。
規則撰寫很是有助於理清思路,也有助於延展性的思考,對我的很是有幫助。