在前面的系列文章中,依次介紹了基於無序列表的順序尋找,基於有序數組的二分尋找,平衡尋找樹,以及紅/黑樹狀結構,下圖是它們在平均以及最差情況下的時間複雜度:
可以看到在時間複雜度上,紅/黑樹狀結構在平均情況下插入,尋找以及刪除上都達到了lgN的時間複雜度。
那麼有沒有尋找效率更高的資料結構呢,答案就是本文接下來要介紹了散列表,也叫雜湊表(Hash Table) 什麼是雜湊表
雜湊表就是一種以 鍵-值(key-indexed) 儲存資料的結構,我們只要輸入待尋找的值即key,即可尋找到其對應的值。
雜湊的思路很簡單,如果所有的鍵都是整數,那麼就可以使用一個簡單的無序數組來實現:將鍵作為索引,值即為其對應的值,這樣就可以快速存取任意鍵的值。這是對於簡單的鍵的情況,我們將其擴充到可以處理更加複雜的類型的鍵。
使用雜湊尋找有兩個步驟: 使用雜湊函數將被尋找的鍵轉換為數組的索引。在理想的情況下,不同的鍵會被轉換為不同的索引值,但是在有些情況下我們需要處理多個鍵被雜湊到同一個索引值的情況。所以雜湊尋找的第二個步驟就是處理衝突 處理雜湊碰撞衝突。有很多處理雜湊碰撞衝突的方法,本文後面會介紹拉鏈法和線性探測法。
雜湊表是一個在時間和空間上做出權衡的經典例子。如果沒有記憶體限制,那麼可以直接將鍵作為數組的索引。那麼所有的尋找時間複雜度為O(1);如果沒有時間限制,那麼我們可以使用無序數組並進行順序尋找,這樣只需要很少的記憶體。雜湊表使用了適度的時間和空間來在這兩個極端之間找到了平衡。只需要調整雜湊函數演算法即可在時間和空間上做出取捨。 雜湊函數
雜湊尋找第一步就是使用雜湊函數將鍵映射成索引。這種映射函數就是雜湊函數。如果我們有一個儲存0-M數組,那麼我們就需要一個能夠將任意鍵轉換為該數組範圍內的索引(0~M-1)的雜湊函數。雜湊函數需要易於計算並且能夠均勻分布所有鍵。比如舉個簡單的例子,使用手機號碼後三位就比前三位作為key更好,因為前三位手機號碼的重複率很高。再比如使用社會安全號碼碼出生年月位元要比使用前幾位元要更好。
在實際中,我們的鍵並不都是數字,有可能是字串,還有可能是幾個值的組合等,所以我們需要實現自己的雜湊函數。 1. 正整數
擷取正整數雜湊值最常用的方法是使用除留餘數法。即對於大小為素數M的數組,對於任意正整數k,計算k除以M的餘數。M一般取素數。 2. 字串
將字串作為鍵的時候,我們也可以將他作為一個大的整數,採用保留除餘法。我們可以將組成字串的每一個字元取值然後進行雜湊,比如
| 1 2 3 4 5 6 7 8 9 10 |
public int GetHashCode(string str) { char [] s = str.ToCharArray(); int hash = 0 ; for ( int i = 0 ; i < s.Length; i++) { |