動畫:什麼是散列表?

散列表

散列表(Hash table,也叫哈希表),是根據鍵(Key)而直接訪問在內存存儲位置的數據結構。也就是說,它經過計算一個關於鍵值的函數,將所需查詢的數據映射到表中一個位置來訪問記錄,這加快了查找速度。這個映射函數稱作散列函數,存放記錄的數組稱作散列表。程序員

散列函數

散列函數,顧名思義,它是一個函數。若是把它定義成 hash(key) ,其中 key 表示元素的鍵值,則 hash(key) 的值表示通過散列函數計算獲得的散列值。算法

散列函數的特色:編程

1.肯定性

若是兩個散列值是不相同的(根據同一函數),那麼這兩個散列值的原始輸入也是不相同的。數組

2.散列碰撞(collision)

散列函數的輸入和輸出不是惟一對應關係的,若是兩個散列值相同,兩個輸入值極可能是相同的,但也可能不一樣。安全

3.不可逆性

一個哈希值對應無數個明文,理論上你並不知道哪一個是。數據結構

「船長,若是同樣東西你知道在哪裏,還算不算丟了。」編程語言

「不算。」函數

「好的,那您的酒壺沒有丟。」動畫

4.混淆特性

輸入一些數據計算出散列值,而後部分改變輸入值,一個具備強混淆特性的散列函數會產生一個徹底不一樣的散列值。設計

常見的散列函數

1. MD5

MD5 即 Message-Digest Algorithm 5(信息-摘要算法5),用於確保信息傳輸完整一致。是計算機普遍使用的雜湊算法之一,主流編程語言廣泛已有 MD5 實現。

將數據(如漢字)運算爲另外一固定長度值,是雜湊算法的基礎原理,MD5 的前身有 MD2 、MD3 和 MD4 。

MD5 是輸入不定長度信息,輸出固定長度 128-bits 的算法。通過程序流程,生成四個32位數據,最後聯合起來成爲一個 128-bits 散列。

基本方式爲,求餘、取餘、調整長度、與連接變量進行循環運算,得出結果。

MD5 計算普遍應用於錯誤檢查。在一些 BitTorrent 下載中,軟件經過計算 MD5 來檢驗下載到的碎片的完整性。

MD5 校驗

2. SHA-1

SHA-1(英語:Secure Hash Algorithm 1,中文名:安全散列算法1)是一種密碼散列函數,SHA-1能夠生成一個被稱爲消息摘要的160位(20字節)散列值,散列值一般的呈現形式爲40個十六進制數。

SHA-1 曾經在許多安全協議中廣爲使用,包括TLS和SSL、PGP、SSH、S/MIME和IPsec,曾被視爲是MD5的後繼者。

散列衝突

理想中的一個散列函數,但願達到

若是 key1 ≠ key2,那 hash(key1) ≠ hash(key2)

這種效果,然而在真實的狀況下,要想找到一個不一樣的 key 對應的散列值都不同的散列函數,幾乎是不可能的,即便是 MD5 或者 由美國國家安全局設計的 SHA-1 算法也沒法實現。

事實上,再好的散列函數都沒法避免散列衝突。

爲何呢?

這涉及到數學中比較好理解的一個原理:抽屜原理。

抽屜原理:桌上有十個蘋果,要把這十個蘋果放到九個抽屜裏,不管怎樣放,咱們會發現至少會有一個抽屜裏面至少放兩個蘋果。這一現象就是咱們所說的「抽屜原理」。

抽屜原理

對於散列表而言,不管設置的存儲區域(n)有多大,當須要存儲的數據大於 n 時,那麼必然會存在哈希值相同的狀況。這就是所謂的散列衝突

散列衝突

那應該如何解決散列衝突問題呢?

經常使用的散列衝突解決方法有兩類,開放尋址法(open addressing)和鏈表法(chaining)。

開放尋址法

定義:將散列函數擴展定義成探查序列,即每一個關鍵字有一個探查序列h(k,0)、h(k,1)、...、h(k,m-1),這個探查序列必定是0....m-1的一個排列(必定要包含散列表所有的下標,否則可能會發生雖然散列表沒滿,可是元素不能插入的狀況),若是給定一個關鍵字k,首先會看h(k,0)是否爲空,若是爲空,則插入;若是不爲空,則看h(k,1)是否爲空,以此類推。

開放尋址法是一種解決碰撞的方法,對於開放尋址衝突解決方法,比較經典的有線性探測方法(Linear Probing)、二次探測(Quadratic probing)和 雙重散列(Double hashing)等方法。

線性探測方法

開放尋址法之線性探測方法

當咱們往散列表中插入數據時,若是某個數據通過散列函數散列以後,存儲位置已經被佔用了,咱們就從當前位置開始,依次日後查找,看是否有空閒位置,直到找到爲止。

以上圖爲例,散列表的大小爲 8 ,黃色區域表示空閒位置,橙色區域表示已經存儲了數據。目前散列表中已經存儲了 4 個元素。此時元素 7777777 通過 Hash 算法以後,被散列到位置下標爲 7 的位置,可是這個位置已經有數據了,因此就產生了衝突。

因而按順序地日後一個一個找,看有沒有空閒的位置,此時,運氣很好正巧在下一個位置就有空閒位置,將其插入,完成了數據存儲。

線性探測法一個很大的弊端就是當散列表中插入的數據愈來愈多時,散列衝突發生的可能性就會愈來愈大,空閒位置會愈來愈少,線性探測的時間就會愈來愈久。極端狀況下,須要從頭至尾探測整個散列表,因此最壞狀況下的時間複雜度爲 O(n)。

開放尋址法之線性探測方法的弊端

二次探測方法

二次探測是二次方探測法的簡稱。顧名思義,使用二次探測進行探測的步長變成了原來的「二次方」,也就是說,它探測的下標序列爲 hash(key)+0hash(key)+1^2[hash(key)-1^2]hash(key)+2^2[hash(key)-2^2]

二次探測方法

以上圖爲例,散列表的大小爲 8 ,黃色區域表示空閒位置,橙色區域表示已經存儲了數據。目前散列表中已經存儲了 7 個元素。此時元素 7777777 通過 Hash 算法以後,被散列到位置下標爲 7 的位置,可是這個位置已經有數據了,因此就產生了衝突。

按照二次探測方法的操做,有衝突就先 + 1^2,8 這個位置有值,衝突;變爲 - 1^2,6 這個位置有值,仍是有衝突;因而 - 2^2, 3 這個位置是空閒的,插入。

雙重散列方法

所謂雙重散列,意思就是不只要使用一個散列函數,而是使用一組散列函數 hash1(key)hash2(key)hash3(key)。。。。。。先用第一個散列函數,若是計算獲得的存儲位置已經被佔用,再用第二個散列函數,依次類推,直到找到空閒的存儲位置。

雙重散列方法

以上圖爲例,散列表的大小爲 8 ,黃色區域表示空閒位置,橙色區域表示已經存儲了數據。目前散列表中已經存儲了 7 個元素。此時元素 7777777 通過 Hash 算法以後,被散列到位置下標爲 7 的位置,可是這個位置已經有數據了,因此就產生了衝突。

此時,再將數據進行一次哈希算法處理,通過另外的 Hash 算法以後,被散列到位置下標爲 3 的位置,完成操做。

事實上,無論採用哪一種探測方法,只要當散列表中空閒位置很少的時候,散列衝突的機率就會大大提升。爲了儘量保證散列表的操做效率,通常狀況下,須要儘量保證散列表中有必定比例的空閒槽位。

通常使用加載因子(load factor)來表示空位的多少。

加載因子是表示 Hsah 表中元素的填滿的程度,若加載因子越大,則填滿的元素越多,這樣的好處是:空間利用率高了,但衝突的機會加大了。反之,加載因子越小,填滿的元素越少,好處是衝突的機會減少了,但空間浪費多了。

鏈表法

鏈表法是一種更加經常使用的散列衝突解決辦法,相比開放尋址法,它要簡單不少。以下動圖所示,在散列表中,每一個位置對應一條鏈表,全部散列值相同的元素都放到相同位置對應的鏈表中。

鏈表法

今日問題

請問能夠對鏈表法進行怎樣的改造,去實現一個更加高效的散列表?

歡迎關注這個會作動畫的程序員👆

相關文章
相關標籤/搜索