數據結構和算法

本部分總結前面介紹的數據結構算法,並討論在不同的情況下如何進行選擇。

通用數據結構:數組、鏈表、樹、哈希表
專用數據結構:棧、隊列、優先級隊列
排序:插入排序、希爾排序、快速排序、歸併排序、堆排序
:鄰接矩陣、鄰接表
外部存儲:順序存儲、索引文件、B-樹、哈希方法

1 通用數據結構                                                           

  若想存儲真實世界中的類似人事記錄、存貨目錄、合同表或銷售業績等數據,則只需要一般用途的數據結構。在本書中屬於這種類型的結構有數組、鏈表、樹和哈希表。他們被稱之爲通用的數據結構是因爲它們通過關鍵字的值來存儲並查找數據,這一點在通用數據庫程序中常見到(棧等特殊結構正好相反,它們只允許存取一定的數據項)。

1.1 速度與算法                    

  通用數據結構可以完全按照速度的快慢來分類:數組和鏈表是最慢的,樹相對較快,哈希表是最快的。
  但是不要有這樣的結論:使用最快的結構永遠是最好的方案。這些最快的結構也有缺陷。首先,它們的程序在不同程度上比數組和鏈表的複雜;其次,哈希表要求預先知道要存儲多少數據,數據對存儲空間的利用率也不是非常高。普通的二叉樹對順序的數據來說,會變成緩慢的O(N)級操作,而平衡樹雖然避免了上述的問題,但是它的程序編制起來卻比較困難。

處理器速度因素
  快速的結構都有缺陷,而計算機的另一個發展因素卻能使低速的結構更加具有吸引力。新計算機的CPU和存取速度每一年都有提升。Moore定律聲明瞭CPU的速度每18個月翻一倍。這造成了早期計算機和現代應用的計算機在性能方面的驚人差異,而且目前沒有任何理由能忍我這個增長速度會減慢。
  幾年前一臺電腦可以在一個可接受的時間內處理100個對象的數組,而現在的計算機則快多了,因此可以在同樣的時間裏處理含有10000個對象的數組。
  請從簡單數據結構入手考慮:除非它們明顯是太慢了,否則就用數組或鏈表編寫程序,看看結構究竟怎樣。如果能在一個可接受的時間內運行完畢,那麼就採用它,不必再找別的。沒有人會留意用的是數組或別的什麼結構,爲什麼一定要拼命地寫出一個平衡樹的算法?甚至必須面對成千上萬、百萬的數據項進行操作時,不妨先看一看數組或鏈表處理表現的情況,這也還是值得的。只有在實驗中發現這些簡單結構的性能太慢時,纔回過頭來採用哪些更加複雜的數據結構。

Java引用的優點

  在操作對象的速度上,Java與其他語言相比有極大的優勢,那是由於對於大多數數據結構來說,Java數據結構只存儲引用而不是實際的對象,因此相對於那些在數據結構中實際爲對象開闢了空間的語言來說,大多數Java算法的執行速度更快。在分析算法時,不是從對象的真實存儲空間出發,因而移動對象的速度也不依賴於對象的大小,而只是考慮對象引用的移動,因此對象本身的大小就不重要了。

數組

  當存儲和操作數據時,在大多數情況下數組是首先應該考慮的結構,數組在下列情況下很有用:
數據量較小
數據量的大小事先可預測
  如果存儲空間足夠大的話,可以放鬆第二條,創建一個足夠大的數組來應付所有可以預見的數據輸入。
  如果插入速度很重要的話,使用無序數組,如果查找速度很重要的話,使用有序數組,並用二分查找。數組元素的刪除總是很慢,這是由於未來填充空出來的單元,平均半數以上的數組元素要被移動,在有序數組中的遍歷是很快的,而在無序數組不支持這種功能。向量類是一種當數據太滿時可以自己擴展空間的數組,向量可以應用於數據量不可預知的情況下,然而,在向量擴充時,要將舊的數據拷入一個新的空間中,這一過程會造成程序明顯的週期性暫停。

鏈表

  如果需要存儲的數據量不能預知或者需要頻繁地插入刪除數據元素時,考慮使用鏈表。當有新的元素加入時,鏈表就開闢新的所需要的空間,所以它甚至可以佔滿全部可用的內存;在刪除過程中,沒必要像數組那樣填補"空白"
  在一個無序的鏈表中,插入是相當快的,查找和刪除卻很慢,因此,與數組一樣,鏈表最好也應用於數據量相對較小的情況。
  對於編程而言,鏈表比數組複雜,但它比樹或哈希表簡單。

二叉搜索樹

  當確認數組和鏈表過慢時,二叉搜索樹是最先應該考慮的結果,樹可以提供快速的O(logN)級的插入、查找和刪除。遍歷的時間複雜度是O(N)級的,這是任何數據結構遍歷的最大值(根據定義,必須訪問所有的數據)。對於遍歷一定範圍內的數據可以很快得出訪問數據的最大值或最小值。
  對於程序來說,不平衡的二叉搜索樹要比平衡的二叉樹簡單得多,但不幸的是,有序數據能將它的性能降至O(N)級,不比一個鏈表好多歲,然而如果可以保證數據是隨機進入的,就不需要用平衡二叉樹。

平衡樹

  在衆多平衡樹中,我們討論了紅黑樹和2-3-4樹,它們都是平衡樹,並且無論輸入數據是否有序,它們都能保證性能爲O(logN)。然而對於實現來說,平衡樹是很複雜的,特別是紅黑樹。如果利用樹的商用類,可以降低複雜性。

哈希表

  哈希表在數據存儲結構中速度最快,這使它成爲計算機而不是人與數據交互時的必需。哈希表通常用於拼寫檢查器和作爲計算機語言編譯器的符號表,這些應用中,程序必須在幾分之一秒的時間裏檢查上千的詞或符號。
  哈希表對插入的順序並不敏感,因此可以取代平衡樹。但是哈希表的編程比平衡樹簡單多了。
  哈希表需要有額外的存儲空間,尤其是對於開放地址法,因爲哈希表用數組作爲基本結構,所以,必須預先精確地知道待存儲的數據量。
  用鏈地址法處理衝突的哈希表是最健壯的實現方法,若能預先精確地知道數據量,在這種情況下,用開放地址法編程最簡單,因爲不需要用到鏈表類。
  哈希表並不能提供任何形式的有序遍歷,或對最大值、最小值進行存取,如果這些功能很重要,使用二叉樹更好些。
通用數據存儲結構的比較

2 專用數據結構                                                         

  專用數據結構有棧、隊列和優先級隊列。這些結構不是爲了用戶可訪問的數據庫而建立的,通常用它們在程序中輔助實現一些算法。
  棧、隊列和優先級隊列是抽象數據類型,它們由一些更加基礎的數據結構如數組、鏈表或堆來實現。這些ADT只提供給用戶間的的接口,一般僅允許插入和訪問或者刪除一個數據項。這些數據項是:
對於棧:最後被插入的數據項
對於隊列:最先被插入的數據項
對於優先級隊列:具有最高優先級的數據項

  棧用在最後被插入數據項訪問的時候,它是一個後進先出的結構。
  棧往往通過數組或鏈表實現,通過數組實現很有效率,因爲最後被插入的數據總是在數組的最後,這個位置的數據很容易被刪除。棧的溢出有可能出現,但當數組的大小被合理規劃後,溢出並不常見,因爲棧很少會擁有大量的數據。
  如果棧擁有許多數據,並且數量不可精確預測(當用棧實現遞歸時),用鏈表比數組更好一些,這是由於從表頭的位置刪除或插入一個元素很方便,除非整個內存滿了,棧的溢出不可能出現。鏈表比數組稍慢一些,因爲對於插入一個新鏈結必須分配內存,從表中某個鏈結點上刪除元素後回收分配內存亦是必須的。

隊列

  隊列用在只對最先被插入數據項訪問的時候,它是一個先進先出的結構。
  同棧相比,隊列同樣可以通過數組和鏈表實現,這兩種方法都很有效率。數組需要附加的程序來處理隊在數組尾部迴繞的情況。鏈表必須是雙端的,這樣才能從一端插入到另一端刪除。 
用數組還是鏈表來實現隊列的選擇是通過數據量是否可以被很好地預測來決定的,如果知道會有多少數據量的話,就使用數組,否則的話就用鏈表。

優先級隊列

  優先級隊列用在只對訪問最高優先級數據項訪問的時候,最高優先級數據項就是含有最大(有時最小)的關鍵字的項。
  優先級隊列可以用有序數組或堆來實現,向有序數組中插入是很慢的,但是刪除很快,使用堆來實現優先級隊列,插入和刪除的時間複雜度都是O(logN)級
  當插入速度不重要時,可以使用數組或雙端鏈表,當數據量可以被預測時,使用數組,當數據量未知時,可以使用鏈表,如果速度很重要的話,選擇堆更好一些。
專用結構的比較

3 排序                                                                     

  當選擇數據結構時,可以先嚐試一種較慢但簡單的排序,例如插入排序。如果採用了這些方法,現代計算機的快速處理速度也有可能在恰當的時間內將較大的數據量排序。
  插入排序對幾乎已排好的文件也很有效,如果沒有太多的元素處於亂序的位置上,操作的時間複雜度大約在O(N)級,這通常發生在往一個已排好序的文件中插入一些新的數據元素的情況。
  如果插入排序顯得太慢,下一步可以嘗試希爾排序,它很容易實現,並且使用起來不會因爲條件不同而性能變化差距太大:Sedgewick估計它的數據量爲5000以下時很有用。
  只有當希爾排序變得很慢時,你才應該使用那些更復雜但更快速的排序方法:歸併排序、堆排序或快速排序。歸併排序需要輔助存儲空間,堆排序需要有一個堆的數據結構,前兩者都比快速排序在某些程度上慢,所以當需要最短的排序時間時經常選擇快速排序。
  然而,快速排序在處理非隨機性能數據時性能不大可靠,因爲那時它的速度有可能退化至O(N^2)級。
  對那些有可能是非隨機性的數據來說,堆排序更加可靠,當快速排序沒有被正確地實現時,它會產生微小偏差,在代碼中細小的錯誤會使它對按某些順序排列的數據無能爲力,而診斷這種情況又相當難。
  下面是幾種排序算法的時間複雜度級別

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章