爲什麼hash table的大小最好取一個不接近2^p的素數

我們拿除法hash做說明,按照hash的原理很容易知道其他類別的hash都是和除法hash同構的。

假設hash函數爲h(k)=k mod m,即m個slot。


m的取值有幾個需要注意的地方:


1.m不能取一個2^p的數。算法導論是這樣解釋的:

這是因爲對一個數除以2^p取餘數相當於只取這個數的最低的p位,高於p位的信息就被丟棄了。

這個原理很容易理解:

假設把15映射到8個槽位的hash表裏,即k=15 m=8。k的二進制表示1111,8的二進制表示1000.取餘數爲7,二進制表示是111,即k的最低3位。這樣相當於不管k的除去最後3位取什麼值,結果都是不變的。


2.m不能取一個接近2^p或10^p的數。

算法導論裏面舉了一個例子:假設m=2^p-1,如果k是一個用p個bit編碼的字符,那麼這些字符無論怎麼組合,hash後得到的結果都是一樣的。

這個理解起來稍微有點困難:

先舉個例子:

假設我們的p取4,也就是m=2^4-1=15。

我們的想要把4個bit編碼的字符所組成的字符串放到hash表中。

這樣每個字符串都可以用一個16進制的數來表示。

打開python,隨便找一組數計算一下:

0x1234%15=10

0x4231%15=10

0x3214%15=10

....

不管怎樣組合,hash得到的結果都是一樣的。

這是爲什麼?


證明一下:

每個字符串的都是p個bit編碼的,我們假設2^p=a因此n個字符的字符串的多項式表示就是

X0+aX1+(a^2)X2+(a^3)X3+(a^(n-1))Xn

對於同樣的字符所組成的字符串,假設2^(ap)這個係數不變,相當於把許多個Xi和Xj做交換。

因爲置換可以表示成有限個對換的乘積。因此,任意兩個字符串可以通過有限次兩兩對換得到。

身爲一個搞計算機的對這個原理並不陌生。我們通常用的各種基於比較的排序算法都是這個原理的具體體現。也就是說任何序列都可以通過兩個元素交換的方法變成標準序列。同時我們也知道,交換的次數的上界是nlogn

對於任意一次對換,假設我們要對換的(a^i)Xi和(a^j)Xj,對換之前表達式是S0=(a^i)Xi+(a^j)Xj+Sn

Sn表示表達式的剩餘部分。

對換之後的表達式S1=(a^j)Xi+(a^i)Xj+Sn。

對換所產生的差值是S1-S0=(a^j)Xi+(a^i)Xj-(a^i)Xi-(a^j)Xj=(a^j-a^j)(Xi-Xj)

我們觀察a^j-a^i這個表達式,可以發現a=1是a^j-a^i=0的恆等解。不管i和j取多少,等式都成立。這意味着a-1是a^j-a^i因式分解後的一個因子。

因此說明,對每一次對換,變化量是a-1的整數倍。也就是a^p-1的整數倍。因此對於任意有限次對換相乘形成的變換,變化量始終是a^p-1的整數倍。

這就證明了不管這個字符串採用什麼樣的組合,他對a^p-1取餘數的結果都是不變的。


雖然2^p-1的例子證明了,但是我還沒有想到其他接近2^p的數所導致的不良後果,但是寧可信其有吧。


3.m的值爲什麼要取一個素數?

這個算法導論中沒有給出解釋,實際上如果學過羣論的話,這個問題是非常好理解的。

因爲對於一個階爲n的羣,他的子羣的階一定是n的因子。(lagrange定理)

所以如果n是一個素數,那麼這個羣就沒有真子羣。

怎麼理解這個原理:

我們做hash table的所需要避免的現象是什麼?

就是我們實際上要放的元素並沒有在hash table中均勻分佈,只是佔用了一部分,大量的空間被浪費了。因爲hash空間是有限的,想要刻意找到無限多個碰撞也是能夠辦到的。我們假設用戶並不是刻意去搗亂,而是碰巧就產生了這個現象。

這種巧合是怎麼形成的?

因爲想往hash表中放的元素本身也是有規律性,這個規律與hash表本身的設計相疊加,產生了這樣的後果。

如果我們m不是一個素數的話,m的每一個因子都可以構造一種循環導致問題產生。

還是舉個例子:

假設m=6時候,我們的槽位相當於<0,1,2,3,4,5>

因爲6=2*3

那麼<0,2,4>,<0,3>都代表了產生問題的情況。

假設用戶的放的k的值滿足每個等差加2,或者每個等差加3,都會導致只佔用hash table中的一部分,而不是全部。

爲了避免這種情況出現,m需要取一個素數。


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