哈希表(散列表)原理詳解


想要知道什么是哈希表,得先了解哈希函數

哈希函數

地址index=H(key)
說白了,hash函數就是根據key計算出應該存儲地址的位置,而哈希表是基於哈希函數建立的一種查找表

幾種常見的哈希函數(散列函數)構造方法

  直接定址法

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

    這里寫圖片描述

  除留余數法 

  • 取關鍵字被某個不大於散列表長度 m 的數 p 求余,得到的作為散列地址。
  • 即 H(key) = key % p, p < m。 

    這里寫圖片描述

  數字分析法 

  • 當關鍵字的位數大於地址的位數,對關鍵字的各位分布進行分析,選出分布均勻的任意幾位作為散列地址。
  • 僅適用於所有關鍵字都已知的情況下,根據實際應用確定要選取的部分,盡量避免發生沖突。

     這里寫圖片描述

  平方取中法 

  • 先計算出關鍵字值的平方,然后取平方值中間幾位作為散列地址。
  • 隨機分布的關鍵字,得到的散列地址也是隨機分布的。

     這里寫圖片描述

  折疊法(疊加法) 

  • 將關鍵字分為位數相同的幾部分,然后取這幾部分的疊加和(舍去進位)作為散列地址。
  • 用於關鍵字位數較多,並且關鍵字中每一位上數字分布大致均勻。 

     這里寫圖片描述

    隨機數法

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

 

構造哈希函數的方法很多,實際工作中要根據不同的情況選擇合適的方法,總的原則是盡可能少的產生沖突。

通常考慮的因素有關鍵字的長度和分布情況、哈希值的范圍等。

如:當關鍵字是整數類型時就可以用除留余數法;如果關鍵字是小數類型,選擇隨機數法會比較好。

什么是哈希表

 哈希表(Hash table,也叫散列表),具有像數組那樣根據隨機訪問的特性,是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表

  我們通過一個具體實例來理解散列表。當學校舉辦校運動會,每個運動員都有一個號碼牌,這個號碼牌的是根據“年級+班級+需序號”組成,比如初一三班的小島的號碼牌為 070311,其中 07表示七年級即初一, 03表示三班, 11表示班上第11個班上參加運動會的序號。

這個時候我們如何存儲運動員的信息,來實現通過號碼牌來查找運動員的信息?

按照以往的經驗,我們可以通過使用數組來存儲,其中號碼牌即為數組的下標,數組的值為運動員的信息。但是這里有一個問題,運動員的號碼牌不是連續的,而申請數組的時候內存空間是連續的,因此會有很多內存空間浪費。

這個時候就可以使用散列表,處理過程如下所示:

    從上圖可以觀察到,我們在存儲運動員信息的時候,不是將整個號碼牌作為數組的下標,而是將號碼牌先進行 hash函數(對100取余)處理后得到的數作為數組的下標,這樣就可以數組的大小大大減小,並且在查找到時候也可以通過號碼牌來查找對應的運動員的信息。

 

index=H(key)

這里的對應關系H稱為散列函數,又稱為哈希(Hash函數),采用散列技術將記錄存儲在一塊連續的存儲空間中,這塊連續存儲空間稱為散列表或哈希表(Hash table)。

Hash Table的查詢速度非常的快,幾乎是O(1)的時間復雜度。

hash就是找到一種數據內容和數據存放地址之間的映射關系。

 

哈希沖突

細心的小伙伴觀察到,如果 hash函數處理后的余數一樣該怎么辦?

即不同key值產生相同的地址,H(key1)=H(key2)

比如我們上面說的存儲3 6 9
3 MOD 3  ==  6 MOD 3   ==   9 MOD 3
此時3 6 9都發生了hash沖突

 解決沖突

設計合理的哈希函數可以減少沖突,但不能完全避免沖突。

所以需要有解決沖突的方法,常見有兩類

 開放定址法

如果兩個數據元素的哈希值相同,則在哈希表中為后插入的數據元素另外選擇一個表項。

當程序查找哈希表時,如果沒有在第一個對應的哈希表項中找到符合查找要求的數據元素,程序就會繼續往后查找,直到找到一個符合查找要求的數據元素,或者遇到一個空的表項。

 

例子

若要將一組關鍵字序列 {1, 9, 25, 11, 12, 35, 17, 29} 存放到哈希表中。

采用除留余數法構造哈希表;采用開放定址法處理沖突。

不妨設選取的p和m為13,由 f(key) = key % 13 可以得到下表。

 

需要注意的是,在上圖中有兩個關鍵字的探查次數為 2 ,其他都是1。

這個過程是這樣的:

①  12 % 13 結果是12,而它的前面有個 25 ,25 % 13 也是12,存在沖突。

我們使用開放定址法 (12 + 1) % 13 = 0,沒有沖突,完成。

②  35 % 13 結果是 9,而它的前面有個 9,9 % 13也是 9,存在沖突。

我們使用開放定址法 (9 + 1) % 13 = 10,沒有沖突,完成。

 

 拉鏈法(鏈表的數組)

將哈希值相同的數據元素存放在一個鏈表中,在查找哈希表的過程中,當查找到這個鏈表時,必須采用線性查找方法。

在這種方法中,哈希表中每個單元存放的不再是記錄本身,而是相應同義詞單鏈表的頭指針。

 

例子

如果對開放定址法例子中提到的序列使用拉鏈法,得到的結果如下圖所示:

我們根據元素的一些特征把元素分配到不同的鏈表中去,也是根據這些特征,找到正確的鏈表,再從鏈表中找出這個元素。

 

元素特征轉變為數組下標的方法就是散列法.    散列法當然不止一種,下面列出三種比較常用的:

除法散列法 

最直觀的一種,公式: index = value % 16 

學過匯編的都知道,求模數其實是通過一個除法運算得到的,所以叫“除法散列法”。

平方散列法 

求index是非常頻繁的操作,而乘法的運算要比除法來得省時(對現在的CPU來說,估計我們感覺不出來),所以我們考慮把除法換成乘法和一個位移操作。公式: 

    index = (value * value) >> 28   (右移,除以2^28。記法:左移變大,是乘。右移變小,是除。)

如果數值分配比較均勻的話這種方法能得到不錯的結果,但我上面畫的那個圖的各個元素的值算出來的index都是0——非常失敗。也許你還有個問題,value如果很大,value * value不會溢出嗎?答案是會的,但我們這個乘法不關心溢出,因為我們根本不是為了獲取相乘結果,而是為了獲取index。

斐波那契(Fibonacci)散列法

平方散列法的缺點是顯而易見的,所以我們能不能找出一個理想的乘數,而不是拿value本身當作乘數呢?答案是肯定的。

1,對於16位整數而言,這個乘數是40503 

2,對於32位整數而言,這個乘數是2654435769 

3,對於64位整數而言,這個乘數是11400714819323198485

    這幾個“理想乘數”是如何得出來的呢?這跟一個法則有關,叫黃金分割法則,而描述黃金分割法則的最經典表達式無疑就是著名的斐波那契數列,即如此形式的序列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377, 610, 987, 1597, 2584, 4181, 6765, 10946,…。另外,斐波那契數列的值和太陽系八大行星的軌道半徑的比例出奇吻合。

    對我們常見的32位整數而言,公式: 

            index = (value * 2654435769) >> 28

    如果用這種斐波那契散列法的話,那就是從左圖到右圖這樣了

      ☞  ☞   

 

注:用斐波那契散列法調整之后會比原來的取摸散列法好很多。 

適用范圍
    快速查找,刪除的基本數據結構,通常需要總數據量可以放入內存。

基本原理及要點
    hash函數選擇,針對字符串,整數,排列,具體相應的hash方法。 

碰撞處理,一種是open hashing,也稱為拉鏈法;另一種就是closed hashing,也稱開放地址法,opened addressing。

 

Hash的應用

1、Hash主要用於信息安全領域中加密算法,它把一些不同長度的信息轉化成雜亂的128位的編碼,這些編碼值叫做Hash值. 也可以說,Hash就是找到一種數據內容和數據存放地址之間的映射關系。

2、查找:哈希表,又稱為散列,是一種更加快捷的查找技術。我們之前的查找,都是這樣一種思路:集合中拿出來一個元素,看看是否與我們要找的相等,如果不等,縮小范圍,繼續查找。而哈希表是完全另外一種思路:當我知道key值以后,我就可以直接計算出這個元素在集合中的位置,根本不需要一次又一次的查找!

舉一個例子,假如我的數組A中,第i個元素里面裝的key就是i,那么數字3肯定是在第3個位置,數字10肯定是在第10個位置。哈希表就是利用利用這種基本的思想,建立一個從key到位置的函數,然后進行直接計算查找。

3、Hash表在海量數據處理中有着廣泛應用。

問題實例(海量數據處理)
    我們知道hash 表在海量數據處理中有着廣泛的應用,下面,請看另一道百度面試題:
題目:海量日志數據,提取出某日訪問百度次數最多的那個IP。
方案:IP的數目還是有限的,最多2^32個,所以可以考慮使用hash將ip直接存入內存,然后進行統計。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM