散列表總結

本文整理自《算法導論》第11章,由於本章有一些概率論知識,因此理解起來比較困難,但是一般只要記住結果即可。
我在面試的時候也被問過:“請問哈希衝突的解決方法有哪些?”,這個問題的答案是:

第一種是鏈接技術,即用雙向鏈表來鏈接哈希值相同的元素。這種方法能夠有良好性能的前提是滿足近似簡單一致散列。
第二種是開放尋址法,而開放尋址法有幾種生成探查尋列的方法,如線性探查、二次探查、二次哈希(double hashing),其中二次哈希用的最多。開放尋址法有良好性能的前提是滿足一致散列。
(以上粗體字都是本文將要講解的內容)


本文定義 n爲實際要插入的關鍵字,m爲槽的個數。

動態集合:支持插入、刪除操作。
靜態集合:只支持查找操作。

字典操作:
INSERT(T,x):將元素x插入T。
DELETE(T,x):將元素x從T中刪除。
SEARCH(T,k):根據關鍵字k查找元素。


一、直接尋址法

定義:每個元素的關鍵字k就是數組的下標,比如一個元素的關鍵字爲2,則T[2]就是元素存放的位置。被尋址的表稱爲直接尋址表。

前提:
(1)關鍵字的全域U較小,不然機器創建一個很大很大的數組不太可能。
(2)關鍵字都是自然數,不然不能直接尋址。
(3)沒有兩個元素的關鍵字相同,即不能“碰撞”。

優點:簡單,方便。並且沒有衝突。
缺點:
(1)關鍵字的全域很大時,如2^64,則計算機不能創建這麼大的數組。
(2)如果關鍵字不是自然數,則不能直接尋址,比如關鍵字是string,則不能T[string]。
(3)如果實際使用的關鍵字只有少數,但是全域很大,則會造成空間的浪費。比如全域爲100000,實際使用了2個關鍵字。


二、散列表

定義:設計一個哈希函數h,如果關鍵字爲k,則h(k)就是哈希表下標位置。

優點:需要的空間少。
缺點:哈希衝突(書中稱爲“碰撞”)。

簡單一致散列:如果任何一個關鍵字k,他映射到m個槽的任何一個的可能性都相同,並且k映射到哪個槽與其他關鍵字獨立無關,則稱爲簡單一致散列。
裝載因子:n/m,即一個槽的鏈表的平均長度。


全域散列:任何一個哈希函數,都存在一個鍵集,使得他們映射到同一個槽中(即誘導發生最壞情況),因此全域散列就有用武之地了,他是預先定義一個有限的散列函數集(就是多個散列函數),等到真正開始執行時,隨機選擇一個散列函數(一旦開始執行,散列函數就不能改變),這樣的優點是對手不知道你要選那個散列函數,看到的只是random()。
全域性質:從散列函數集中隨機選擇一個函數,兩個關鍵字碰撞的概率至多1/m。

結論:
(1)在散列函數集是全域的條件下,成功查找的期望時間至多1+n/m。
(2)在散列函數集是全域的條件下,不成功查找的期望時間至多n/m。

證明結論:
 

 

選取散列函數是一門很大的學問,但是有一些啓發式的函數,如:
1.除法散列法:h(k)= k mod m 
        注意點:
            (1)m不能太小,m不能是2的冪次,m一定是質數。
            (2)這個方法的優點是簡單。
2.乘法散列法:h(k)=floor(m(kA mod 1)),這個方法一般比除法散列法好,因爲乘法比除法快。
        注意點:
            (1)一般A=(根號5 - 1)/2
            (2)一般m是2的冪次。
如果不理解乘法散列法,可以做做算法導論11.3-4。

完全散列:適用於靜態集合(只有search操作),利用兩級哈希,使得最壞情況下查找只需要O(1)。原理如下圖:

 
完全散列的要求:第二個散列函數必須是沒有碰撞發生的。


三、解決碰撞的方法

有沒有完全避免碰撞的方法呢?實際上是沒有的,根據鴿巢原理,n只要大於m,一定至少有一個槽放了多於1個元素。

1、鏈接法

用雙向鏈表來鏈接哈希值相同的元素。這種方法能夠有良好性能的前提是滿足近似簡單一致散列。
簡單一致散列:如果任何一個關鍵字k,他映射到m個槽的任何一個的可能性都相同,並且k映射到哪個槽與其他關鍵字獨立無關,則稱爲簡單一致散列。
結論:
INSERT操作的最壞情況時間爲O(1)
DELETE操作的最壞情況時間爲O(1)
查找不成功的SEARCH操作的期望運行時間爲O(1+n/m),最壞情況時間爲O(n),當n=O(m)時,期望時間爲O(1)
查找成功的SEARCH操作的期望運行時間爲O(1+n/m),最壞情況時間爲O(n),當n=O(m)時,期望時間爲O(1)

爲了更好地理解鏈接法的隨機性,可以做做算法導論11-2。


證明:簡單一致散列的假設下,查找不成功的期望時間爲O(1+n/m)
首先hash函數需要O(1),然後設Xk爲T[k]的鏈表的期望長度,我們已知期望長度爲n/m,查找不成功會遍歷一個鏈表,因此查找不成功的期望時間爲O(1+n/m)。

證明:簡單一致散列的假設下,查找成功的期望時間爲O(1+n/m)
 

 

2、開放尋址法

開放尋址法是另一種解決碰撞的方法。
定義:將散列函數擴展定義成探查序列,即每個關鍵字有一個探查序列h(k,0)、h(k,1)、...、h(k,m-1),這個探查序列一定是0....m-1的一個排列(一定要包含散列表全部的下標,不然可能會發生雖然散列表沒滿,但是元素不能插入的情況),如果給定一個關鍵字k,首先會看h(k,0)是否爲空,如果爲空,則插入;如果不爲空,則看h(k,1)是否爲空,以此類推。

特點:散列表的每個槽只能放一個元素,因此當n==m時,最終會發生不能再插入元素的情況,n/m<=1。

一致散列:每個關鍵字所對應的探查序列是0...m-1的m!種排列的每個可能性都相同,並且和其他關鍵字獨立無關。
開放尋址法好性能的前提是一致散列。

缺點:不支持刪除操作,只支持INSERT、SEARCH操作,因此如果有刪除操作,就用鏈接法。算法導論11.4-2要求實現DELETE操作。

生成探查序列的方法有:
(1)線性探查:h(k,i)=(h'(k)+i) mod m,可能有“一次羣集”問題,即隨着插入的元素越來越多,操作時間越來越慢。
(2)二次探查:h(k,i)=(h'(k)+ai+bi^2) mod m,可能有“二次羣集”問題,即如果h(k1,0)=h(k2,0),則探查序列就一致。
(3)二次哈希:h(k,i)=(h1(k)+ih2(k)) mod m ,要求m和h2(k)互質,不然探查序列不能覆蓋到整個下標。算法導論11.4-3證明了這點。
其中二次哈希最好,因爲他能夠生成m^2種(離m!最接近)排列,而線性探查、二次探查只能生成m種排列,而理想中如果滿足一致散列的話,則會生成m!種排列。

結論:
(1)在一致散列的條件下,不成功查找的期望探查數至多爲 1/(1-n/m)。
(2)在一致散列的條件下,成功查找的期望探查數至多爲 1/(n/m) * ln(1/(1-n/m)),當然也可以至多爲1/(1-n/m)[MIT開放課程中是這個,其實都差不多,下面我會證明]。
(3)在一致散列的條件下,插入的期望探查數至多爲 1/(1-n/m)。

證明結論:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章