數據結構:哈希表以及哈希衝突的解決方案

前言

基於先前的學習計劃,最近打算深刻學習Java的集合類,首先要研究的就是HashMap,在學習HashMap前,我花了幾天時間溫習了一下類中用到的數據結構 (哈希表,二叉樹),並決定把所學的知識記錄寫成文章,本文講述的就是關於哈希表的知識。算法

什麼是哈希表

在以前的博客文章裏,咱們簡單介紹了數據結構的幾種分類,其中就包括哈希表,也稱散列表,從根本上來講,一個哈希表包含一個數組,經過特殊的關鍵碼(也就是key)來訪問數組中的元素。哈希表的主要思想是經過一個哈希函數, 把關鍵碼映射的位置去尋找存放值的地方 ,讀取的時候也是直接經過關鍵碼來找到位置並存進去。數組

最直接的例子就是字典,例以下面的字典圖,若是咱們要找 「啊」 這個字,只要根據拼音 「a」 去查找拼音索引,查找 「a」 在字典中的位置 「啊」,這個過程就是哈希函數的做用,用公式來表達就是:f(key),而這樣的函數所創建的表就是哈希表。比起數組和鏈表查找元素時須要遍歷整個集合的狀況來講,哈希代表顯方便和效率的多。
數據結構

常見的哈希算法

哈希表的組成取決於哈希算法,也就是哈希函數的構成,下面列舉幾種常見的哈希算法。函數

1) 直接定址法性能

  • 取關鍵字或關鍵字的某個線性函數值爲散列地址。
  • 即 f(key) = key 或 f(key) = a*key + b,其中a和b爲常數。

2) 除留餘數法學習

  • 取關鍵字被某個不大於散列表長度 m 的數 p 求餘,獲得的做爲散列地址。
  • 即 f(key) = key % p, p < m。這是最爲常見的一種哈希算法。

3) 數字分析法設計

  • 當關鍵字的位數大於地址的位數,對關鍵字的各位分佈進行分析,選出分佈均勻的任意幾位做爲散列地址。
  • 僅適用於全部關鍵字都已知的狀況下,根據實際應用肯定要選取的部分,儘可能避免發生衝突。

4) 平方取中法3d

  • 先計算出關鍵字值的平方,而後取平方值中間幾位做爲散列地址。
  • 隨機分佈的關鍵字,獲得的散列地址也是隨機分佈的。

5) 隨機數法code

  • 選擇一個隨機函數,把關鍵字的隨機函數值做爲它的哈希值。
  • 一般當關鍵字的長度不等時用這種方法。

哈希衝突

哈希表由於其自己的結構使得查找對應的值變得方便快捷,但也帶來了一些問題,以上面的字典圖爲例,key中的一個拼音對應一個字,那若是字典中有兩個字的拼音相同呢?例如,咱們要查找 「按」 這個字,根據字母拼音就會跳到 「安」 的位置,這就是典型的哈希衝突問題。這個時候用公式表達就是:blog

key1 ≠  key2  , f(key1) = f(key2)

通常來講,哈希衝突是沒法避免的,若是要徹底避免的話,那麼就只能一個字典對應一個值的地址,也就是一個字就有一個索引 (就是兩個索引),這樣一來,空間就會增大,甚至內存溢出。

哈希衝突的解決辦法

常見的哈希衝突解決辦法有兩種,開放地址法和鏈地址法。

1、開放地址法

開發地址法的作法是,當衝突發生時,使用某種探測算法在散列表中尋找下一個空的散列地址,只要散列表足夠大,空的散列地址總能找到。按照探測序列的方法,通常將開放地址法區分爲線性探查法、二次探查法、雙重散列法等。

這裏爲了更好的展現三種方法的效果,咱們用以一個模爲8的哈希表爲例,採用除留餘數法,往表中插入三個關鍵字分別爲26,35,36的記錄,分別除8取模後,在表中的位置以下:

這個時候插入42,那麼正常應該在地址爲2的位置裏,但由於關鍵字30已經佔據了位置,因此就須要解決這個地址衝突的狀況,接下來就介紹三種探測方法的原理,並展現效果圖。

1) 線性探查法:

fi=(f(key)+i) % m ,0 ≤ i ≤ m-1

探查時從地址 d 開始,首先探查 T[d],而後依次探查 T[d+1],…,直到 T[m-1],此後又循環到 T[0],T[1],…,直到探查到有空餘的地址或者到 T[d-1]爲止。

插入42時,探查到地址2的位置已經被佔據,接着下一個地址3,地址4,直到空位置的地址5,因此39應放入地址爲5的位置。

缺點:須要不斷處理衝突,不管是存入仍是査找效率都會大大下降。

2) 二次探查法

fi=(f(key)+di) % m,0 ≤ i ≤ m-1

探查時從地址 d 開始,首先探查 T[d],而後依次探查 T[d+di],di 爲增量序列1^2,-1^2,2^2,-2^2,……,q^2,-q^2 且q≤1/2 (m-1) ,直到探查到 有空餘地址或者到 T[d-1]爲止。

缺點:沒法探查到整個散列空間。

因此插入42時,探查到地址2被佔據,就會探查T[2+1^2]也就是地址3的位置,被佔據後接着探查到地址7,而後插入。

3) 雙哈希函數探測法

fi=(f(key)+i*g(key)) % m (i=1,2,……,m-1)

其中,f(key) 和 g(key) 是兩個不一樣的哈希函數,m爲哈希表的長度

步驟:

雙哈希函數探測法,先用第一個函數 f(key) 對關鍵碼計算哈希地址,一旦產生地址衝突,再用第二個函數 g(key) 肯定移動的步長因子,最後經過步長因子序列由探測函數尋找空的哈希地址。

好比,f(key)=a 時產生地址衝突,就計算g(key)=b,則探測的地址序列爲 f1=(a+b) mod m,f2=(a+2b) mod m,……,fm-1=(a+(m-1)b) % m,假設 b 爲 3,那麼關鍵字42應放在 「5」 的位置。

哈希表性能

​ 哈希表的特性決定了其高效的性能,大多數狀況下查找或者插入元素的時間複雜度能夠達到O(1), 時間主要花在計算hash值上, 然而也有一些極端的狀況,最壞的就是hash值全都映射在同一個地址上,這樣哈希表就會退化成鏈表,例以下面的圖片:
當hash表變成圖2的狀況時,時間複雜度會變爲O(n),效率瞬間低下,因此,設計一個好的哈希表尤爲重要,如HashMap在jdk1.8後引入的紅黑樹結構就很好的解決了這種狀況。

相關文章
相關標籤/搜索