散列表(Hash table,也叫哈希表),是根據鍵(Key)而直接訪問在內存存儲位置的數據結構。也就是說,它經過計算一個關於鍵值的函數,將所需查詢的數據映射到表中一個位置來訪問記錄,這加快了查找速度。這個映射函數稱作散列函數,存放記錄的數組稱作散列表。程序員
散列函數,顧名思義,它是一個函數。若是把它定義成 hash(key) ,其中 key 表示元素的鍵值,則 hash(key) 的值表示通過散列函數計算獲得的散列值。算法
散列函數的特色:編程
若是兩個散列值是不相同的(根據同一函數),那麼這兩個散列值的原始輸入也是不相同的。數組
散列函數的輸入和輸出不是惟一對應關係的,若是兩個散列值相同,兩個輸入值極可能是相同的,但也可能不一樣。安全
一個哈希值對應無數個明文,理論上你並不知道哪一個是。數據結構
「船長,若是同樣東西你知道在哪裏,還算不算丟了。」編程語言
「不算。」函數
「好的,那您的酒壺沒有丟。」動畫
輸入一些數據計算出散列值,而後部分改變輸入值,一個具備強混淆特性的散列函數會產生一個徹底不一樣的散列值。設計
MD5 即 Message-Digest Algorithm 5(信息-摘要算法5),用於確保信息傳輸完整一致。是計算機普遍使用的雜湊算法之一,主流編程語言廣泛已有 MD5 實現。
將數據(如漢字)運算爲另外一固定長度值,是雜湊算法的基礎原理,MD5 的前身有 MD2 、MD3 和 MD4 。
MD5 是輸入不定長度信息,輸出固定長度 128-bits 的算法。通過程序流程,生成四個32位數據,最後聯合起來成爲一個 128-bits 散列。
基本方式爲,求餘、取餘、調整長度、與連接變量進行循環運算,得出結果。
MD5 計算普遍應用於錯誤檢查。在一些 BitTorrent 下載中,軟件經過計算 MD5 來檢驗下載到的碎片的完整性。
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)+0
,hash(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 表中元素的填滿的程度,若加載因子越大,則填滿的元素越多,這樣的好處是:空間利用率高了,但衝突的機會加大了。反之,加載因子越小,填滿的元素越少,好處是衝突的機會減少了,但空間浪費多了。
鏈表法是一種更加經常使用的散列衝突解決辦法,相比開放尋址法,它要簡單不少。以下動圖所示,在散列表中,每一個位置對應一條鏈表,全部散列值相同的元素都放到相同位置對應的鏈表中。
請問能夠對鏈表法進行怎樣的改造,去實現一個更加高效的散列表?
歡迎關注這個會作動畫的程序員👆