一句話概括一下就是:先理解再動手,先實現再優化。
首先肯定是要仔細閱讀所提供的jml規格,充分理解規格的作用和其需要完成的任務,在動手之前也先想好要用到什麼內容(選用什麼容器啦,要用什麼資料啦,需不需要修改原本的資料等等),這一點無可厚非。
第二步就是先試著動動手,暫不考慮效能以及該規格與原有內容之間的內在聯絡,只是按照規格開始一步一步地實現,這一步只要能正確實現規格要求的內容即可(tle警告)。
第三步主要就是效能問題了。筆者推薦在此之前先進行第二步是因為,第二步實現的**雖然效能可能不太理想,但有關規格的理解相比於空想可謂提公升顯著。筆者的優化主要涉及到了以下內容:用到的資料是否可以和原有資料或結構產生聯絡,如果有的話是否可以通過對原有資料進行修改或替換,這樣可以避免新建資料產生的複雜問題;用到容器的規格是否可以進一步優化,以縮短時間複雜度,或是否可與原有資料產生聯絡,以加強新內容與原來內容的聯絡,比如把person
類中的acquaintance
和value
合併;第二步中實現的演算法是否可以優化,比如在取最短帶權路徑是把遍歷換成dijkstra
演算法。
筆者的測試方法和策略是:重難點函式手動驗證邊界資料點,其餘函式和效能問題用自動生成資料和對拍機實現。
舉個栗子:在第一次作業中,qbs
函式是乙個重點內容,通過理解我們可以知道該函式是用來統計互動系統中群體(相互聯絡的人組成的集體)的個數。所以該函式的正確性通過手動輸入極限資料,比如0等一些邊界數值,在確保正確之後,通過自動資料生成器生成大資料來看程式執行的時間。
由於各個物件的id
具有唯一性,所以採用的是hashmap
來充當絕大多數的容器,其中建立了從id
到具體物件的對映關係。這樣的好處是,絕大部分的內容由庫函式提供,人為新增的**較小,且相比於arraylist
等容器,其所用的時間大大減少。
但仍有少部分的容器選擇是linkedlist
,比如person
類中的messages
,原因是每次新增新message
時要求加在第一位,而hashmap
是無序的,無法實現類似的功能。
還有用到的就是hashset
,由於和每個人有聯絡的人可能有很多,所以在儲存關係的時候使用了型別為hashmap>
的容器。
使用經驗的話,筆者將容器分成了兩類:hashmap
和其他。如果要儲存的資料有獨一無二的屬性,且對儲存順序沒有要求,則首選hashmap
;如果不滿足上述條件,或者是有其他要求,再按照要求選擇,比如要求對儲存資料進行排序的話,筆者可能就選擇treemap
了。
由於筆者的**在架構中穿插有效能方面的優化,所以就放在一起說啦~三次作業中的異常類都很類似,所以將所有的異常類單獨抽出來說。
異常類內部設定乙個全域性變數num
用來統計呼叫該異常類的次數,再設定乙個hashmap
型別的變數,用來記錄呼叫的person
的id
和該id
呼叫次數的對映。然後再加上print
函式用來輸出對應的資訊,這樣就完成了異常類的設計。
第一次的作業與後兩次相比較為基礎,只是簡單地實現了一下基礎的操作,大部分都可以直接照著規格寫,唯一涉及到了架構設計的地方就是qbs
函式和iscircle
函式了。
如果按照規格提供的做法來實現qbs
函式的話,由於其中通過遍歷不斷地呼叫iscircle
函式,所以會導致超時。於是,筆者的做法是,新建乙個hashmap>
的容器,用來表示和某一id
相關聯的使用者的id
,這樣可以避免iscircle
函式中的遍歷問題,從而大大減少**的時間複雜度。具體操作為:因為每一次新增使用者間的關係的時候都是通過addrelation
函式,因此在該函式中,加入一點修改,每次新增時進行判斷,如果person2
本身就在person1
的關係列表中,則不做任何操作;如果不在的話,則合併二者的關係列表,遍歷其中的所有使用者,並將他們的關係列表也改成合併後的關係列表,即可實現對關係的實時新增。
第二次作業雖然實現上增加了更多的函式,但對效能的要求沒有那麼強烈。筆者只是簡單優化了新的有關年齡的函式和對上次的qbs
函式更進一步地改進。
首先是求年齡平均值和方差的函式的規格給出的是遍歷的方法,這樣的時間複雜度很高,而我們也可以觀察到,每乙個使用者新增到group
的時候,都是通過addperson
函式,同樣刪除也是僅呼叫delperson
函式,所以筆者在group
類中新建了兩個整型變數來儲存組內的年齡和和年齡平方和,以此來化簡求平均值和方差的函式。值得一說的是,筆者選擇了儲存平方和然後用(sumofage2 - 2 * sumofage * agemean + size * agemean * agemean) / size
這樣的公式來求方差的值,之所以沒有用和的平方和平方和進行運算,是因為後者可能在整數除法中刪掉除不盡的部分,從而導致精度損失。
再者就是第一次中的qbs
函式,因為筆者在做第二次作業的過程中對整體構造有了進一步的理解,加上向大佬的詢問,筆者發現每一次新建person
的時候,對應的塊數應該要加一,而每次合併兩個關係列表的時候對應的塊數要減一。於是乎,發現了新大陸的筆者在network
中新建了變數來表示當前的塊數,然後在新建使用者和合併使用者間的關係列表的時候對其進行操作,之後在qbs
函式中直接將其輸出,使得時間有了較大程度的提公升。
第三次作業在難度上是三次中最大的,需要補充的**較多,且大多數是在原有基礎上做的維護,如果對**整體框架不熟悉的話可能會產生較大的問題。
但第三次作業在優化上的空間不大,只是需要注意最後的sendindirectmessage
函式中涉及到圖論的帶權最短路徑問題。筆者採用的是小優化後的dijkstra
演算法:首先建立兩個hashmap
,乙個hashset
和乙個startperson
物件,兩個hashmap
中乙個用來存已經找到最短路徑的點的id
和最短路徑的數,另乙個存還沒有找到的點和到其需要的路徑數(記錄到未找到最短路徑的點的權值),hashset
存未找到最短路徑的點的id
,startperson
則表示當前新找到的點。完整演算法如下:首先遍歷startperson
和未找到最短路徑的點之間的權值,用新的權值更新負責記錄的hashmap
,然後從其中找到最短的點,將startperson
更新為這一點,然後把它從表示未找到的集合移動到表示找到的集合中,一直重複上述過程,直到找到所求的點。這樣做的話,相比於遍歷的演算法時間複雜度有了巨大的提公升。
在這裡讀者大大可能會產生疑問:是應新建資料還是修改原有資料?筆者認為一味地新建只會讓**變得冗餘,許多資料只有一處使用,白白占用空間;而如果全部用原有的資料,會使得每乙個資料都變得複雜,以後修改時很容易」牽一髮而動全身「,也加大了對**的維護難度。所以說,關於這一點,筆者認為如果關聯性特別大,就修改原有的資料,否則的話還是新建變數吧。這樣修改完圖模型後,就可以一眼看見新的工作是什麼了,從而進行很好地維護。
BUAA OO 第三單元總結
本單元主要學習根據課程組提供的介面中的jml規格,實現自己的方法。jml是一種形式化的 面向j a的行為介面規格語言,起初會比較陌生,但是在熟悉後還是比較直白易懂的,而且比較直接也非常詳細地給出了思路,當然了,本次作業中有許多處的容器和資料結構是需要自己設計和優化的。這也是三次作業主要的工作量。不過...
BUAA OO 第三單元總結
本次作業中的規格實現我採取了巨集觀到具體 增量修改 改不了就重構 的策略,大致進行以下幾個步驟。1.首先瀏覽整體某個類中所儲存的資料和需要實現的方法,梳理方法的大致含義以及方法與資料儲存之間的關係。2.找到幾個比較複雜 一般是jml很長 的方法,現根據規格給出的方法大體實現一遍,計算複雜度。3.若複...
BUAA OO 第三單元作業總結
本單元作業由於設計架構已經基本規定好,只需要實現部分介面的功能即可,所以歷次作業的實現幾乎相同,都遵循以下幾個步驟 閱讀指導書,先大體梳理一遍需要實現的是哪些部分 閱讀jml整體規格,關注當前介面對應的類需要維護哪些屬性以及該類的規格大小,並大體閱讀一下有哪些方法以選擇適當的容器 細讀jml規格,先...