1. 程式人生 > >資料結構學習筆記六(散列表)

資料結構學習筆記六(散列表)

一、散列表的由來

       散列表用的就是陣列支援按照下標隨機訪問的時候時間複雜度是O(1)的特性。我們通過雜湊函式把元素的鍵值對映為下標,然後將資料儲存在陣列中對應下標的位置。當我們按照鍵值查詢元素時,我們用同樣的雜湊函式,將鍵值轉換為陣列下標,從對應的陣列下標的位置取資料。
散列表用的是陣列支援按照下標隨機訪問資料的特性,所以散列表就是陣列的一種擴充套件,由陣列演化而來。沒有陣列,就沒有散列表。

二、雜湊函式

       我們可以把雜湊函式定義為hash(key),其中key表示元素的鍵值,hash(key)的值表示經過雜湊函式計算得到的雜湊值。
1.雜湊函式的設計要求

  • 雜湊函式計算得到的雜湊值是一個非負整數
  • 如果key1=key2,那hash(key1)==hash(key2)

2.處理雜湊衝突

  • 開放定址法
  • 連結串列法

三、散列表的應用

1.Word文件中單詞檢查功能的實現
       常見的英文單詞有20萬個左右,假設單詞的平均長度是10個字母,平均一個單詞佔10個位元組的記憶體空間,那20萬英文單詞大約佔2MB的記憶體空間,完全可以放到記憶體裡面。我們可以用散列表來儲存整個英文單詞詞典。當用戶輸入某個英文單詞的時候,我們拿使用者輸入的單詞在散列表中查詢。如果查詢,則說明拼寫正確;如果沒有查到,則說明拼寫可能有誤,給予提示。
2.假設我們有10萬條URL訪問日誌,如何按照訪問次數給URL排序?


       遍歷10萬條資料,以URL為key,訪問次數為value,存入散列表,同時記錄下訪問次數的最大值K,時間複雜度為O(n)。如果K不是很大,可以使用桶排序,時間複雜度為O(n)。如果K非常大(比如大於10萬),就使用快速排序,時間複雜度為O(nlogn)。
3.有兩個字串陣列,每個陣列大約有10萬條字串,如何快速找出兩個陣列中相同的字串?
       以第一個字串陣列構建散列表,key為字串,value為出現次數。再遍歷第二個字串陣列,以字串為key在散列表中查詢,如果value大於0,則說明存在相同字串。時間複雜度為O(n)。

四、開放定址法與連結串列法的區別

1.開放定址法
       當資料量比較小、裝載因子小的時候,適合採用開放定址法。這也是Java中ThreadLocalMap使用開放定址發放解決雜湊衝突的原因。
優點: 散列表中的資料都儲存在陣列中,可以有效地利用CPU快取加快查詢速度。而且這種方法實現的散列表,序列化起來比較簡單。
缺點: 用開放定址法解決衝突的散列表,刪除資料的時候比較麻煩。而且,比起連結串列法,衝突的代價更高。
2.連結串列法
       基於連結串列的雜湊衝突處理方法比較適合儲存大物件、大資料量的散列表。而且,比起開放定址法,連結串列法更加靈活,支援更多的優化策略,比如用紅黑樹代替連結串列。
優點: 連結串列法相對於開放定址法,對大裝載因子的容忍度更高。
缺點: 連結串列中的節點是零散地分佈在記憶體中,不是連續的,所以對CPU緩衝是不友好的,這方面對於執行效率也有一定影響。

五、HashMap

1.初始大小
       HashMap預設的初始大小是16,當然這個預設值是可以設定的。如果事先知道資料量的大概大小,通過修改預設初始大小,減少動態擴容的次數,這樣會大大提高HashMap的效能。
2.裝載因子和動態擴容
       最大裝載因子預設是0.75,當HashMap中元素個數超過0.75*capacity的時候,就會啟動擴容,每次擴容都會擴容為原來的兩倍大小。
3.雜湊衝突解決方法
       HashMap底層採用鏈地址法來解決衝突。在JDK1.8版本中,為了對HashMap做進一步優化,我們引入了紅黑樹。當連結串列長度太長(預設超過8)時,連結串列就轉換為紅黑樹。我們可以利用紅黑樹快速增刪改查的特點,提高HashMap的效能。當紅黑樹結點個數小於8個時,又會將紅黑樹轉化為連結串列。因為在資料量較小的情況下,紅黑樹要維護平衡,比起連結串列來,效能上的優勢並不明顯。

六、實現一個工業級的散列表

要滿足以下要求:

  • 支援快速的查詢、插入、刪除操作;
  • 記憶體佔用合理,不能浪費過多的記憶體空間;
  • 效能穩定,極端情況下,散列表的效能也不會退化到無法接受的地步。