1. 程式人生 > 其它 >OO第三單元回顧總結

OO第三單元回顧總結

目錄

前言

本單元圍繞JML進行規格化設計,契約式程式設計的思想基本上貫穿了整個單元。

本單元最大的新體驗在於:作為大工程中某一部分程式碼的實現者,履行應盡的責任,而非像前幾個單元一樣自行設計完整架構並完成所有程式碼。更直觀的感受在於:由於課程組提供了細化到每一個類和類方法的JML描述,完成作業更像是做一道道小題,頗有一種“闖關”的快感,寫起程式碼來體驗極佳!

儘管如此,本單元依然存在一些難點,也存在許多值得探討的課題,接下來將詳細描述。

一、架構設計

本單元主要需要設計Network中的GruopPerson例項的生成、關係建立和查詢等方法。其中涉及一些圖論知識。

1. 第一次作業

第一次作業的難點在於完成query_circlequery_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方法設計到三種異常。因此,在構造測試資料時,需要包含:

  1. 正常的測試資料:存在idid1Person,存在idid2Group,且Person目前不在Group
  2. 涉及GroupIdNotFoundException的測試資料:不存在idid2Group
  3. 涉及PersonIdNotFoundException的測試資料:不存在idid1Person
  4. 涉及EqualPersonIdException的測試資料:idid1Person已經位於idid2Group

三、效能分析

本單元嚴格限制了程式碼執行時間,以下總結了可能出現效能問題的指令,與本人的解決方案。

1. query_circle與query_block_sum

除了上文中提到的,這兩條指令使用並查集完成外。在實現過程中,還需要進行路徑壓縮。具體而言,路徑壓縮通過重構並查集的樹狀結構,減少查詢根結點時間。以下圖為例:

在查詢到結點3的根結點為結點1之後,使結點3直接指向結點1,由此可以減少下次查詢結點3根結點時花費的時間。

2. query_group_value_sum

如果在每次收到qgvs指令後,都通過二重迴圈,遍歷Group中兩兩結點的關係,則時間複雜度為O(n^2),無法通過本單元測試。解決方案是,在Group中維護一個變數valueSum儲存此查詢的答案。valueSum需要在遇到以下指令時檢查是否需要更新:

  1. add_relation:需要檢查兩個Person是否都位於同一個Group,如果是,則需要更新valueSum
  2. add_to_group:需要檢查加入的Person與目前位於Group中的每一個Person是否有關係,如果是,則需要更新valueSum
  3. del_from_group:需要檢查刪除的Person與目前位於Group中的其他每一個Person是否有關係,如果是,則需要更新ValueSum
3. query_least_connection

如上文所述,利用依賴並查集的Kruskal演算法,可以滿足時間限制。

4. send_indirect_message

如上文所述,利用堆優化的Dijkstra演算法,可以滿足時間限制。

四、Network拓展

本人思路如下:構建AdvertiserProducerCustomer三個介面,這三個介面均繼承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規格語言實現程式碼。就筆者而言,這是頭一次涉及到多人合作完成程式碼的方法和思想,有非常獨特的體驗。

在完成單元作業方面,本單元確實難度較低,學習過程非常輕鬆愉快。同時,本單元間接幫助筆者回憶了離散數學和資料結構的一些相關知識。也只有在這種情況下,筆者才能意識到自己對於過去學過的知識遺忘之快,可見過去學習並不那麼紮實。

最後,希望能順利完成第四單元,繼續加油!