哈希取余法、哈希表大小取質數的問題


原文地址


哈希取余法、哈希表大小取質數的問題

  1. hashing | planetmath.org http://planetmath.org/node/33326
  2. good hash table primes | planetmath.org http://planetmath.org/goodhashtableprimes
  3. 哈希函數取余法除數為何要取質數? - SegmentFault http://segmentfault.com/q/1010000000593741
  4. 為何哈希函數取余法要避免2的冪? - SegmentFault http://segmentfault.com/q/1010000000593556
  5. 哈希表詳解 - 承續緣的信仰 - 博客頻道 - CSDN.NET http://blog.csdn.net/liangbopirates/article/details/9753599

  6. 之前一直好奇,為什么哈希表的大小需要取質數,而且還要取最好遠離2的冪次方的質數?
    任意一個數x mod m的值,與m的值應該沒有什么關系吧。
    看了上面一些講的,自己總結一下。

    第一個鏈接planetmath講的hashing里面,我覺得說的挺對的,哈希函數的實質,就是把一個鍵值空間為K的映射到hashtable的空間T中。
    hash函數的設置有兩個主要的要求:
    1.hash的計算要快,效率要高,即hash的計算公式要比較簡單,不要太復雜
    2.hash的沖突率要低
    這里它說了兩種hash函數的構造方法,一個是除,一個是乘:h(k)=f(k)(modn);h(k)=⌊n⋅((f(k)⋅r)(mod1))⌋,r是0到1的數。
    雖然都想要那種能夠one to one的完美hash函數,不過一般很難辦到,所以就有hash沖突,hash沖突就是兩個不同的鍵值k1,k2,
    但是h(k1)=h(k2),這就造成沖突。
    這里我們定義一個負載因子:l=t/h,其中t是代表hashtable中已經被填好的槽,h是hashtable的總槽數。
    很明顯,當l接近於1的時候,沖突是不可避免的。
    為了避免沖突的話,有三種解決方案:
    1.把hashtable的大小開的足夠大,比鍵值空間K的范圍大得多
    2.采用拉鏈法
    3.選擇一個完美hash函數,是one to one的。
    考慮option 1,設hash表中實際被填滿的槽為A,A是K的子集,則有|A|<c|T|,實驗效果發現,c取1/2到3/4
    之間比較好。
    考慮option 2,拉鏈法就沒有沖突不沖突了,如果沖突了,直接再申請一塊內存空間存下來就好了。
    考慮option 3,所謂的完美one to one的hash函數,其實主要是由目標數據和可用內存空間兩個一起決定的
    舉個簡單例子,h(k)=k肯定是一個完美hash,不過你的內存是不是能夠把它們都保存下來就不一定了。
    然后提了兩種hash:
    1.接近完美hash的函數,可以把n個映射到n個,沒有沖突,這個需要好好構造。
    2.能夠直接進行排序的,如果k1<k2,就有h(k1)<h(k2),這樣相當於節約了排序的時間消耗o(nlog(n)),當K
    小於T的時候是可行的,不過由於這樣肯定會在hash函數中增加額外的計算,這樣無形中對hash的效率是有
    一定影響的。

    第二個鏈接主要是對hash表的大小取質數進行了一個簡單的介紹。
    它提供了一個hash表大小取值list,list里面的書都有3個特點:
    1.都是質數
    2.都是前一個質數的2倍小一點點
    3.都是遠離2的冪次方的數
    至於為什么這么選的原因
    第一個質數,從第三個和第四個鏈接里面看的話,個人覺得滕亦飛的答案比較正確。其實如果鍵值的取值是均勻的,沒有什么特點,當K<T的時候,其實不管T的取值是多少,都是最優的,概率都是平均的。不過如果當K>T了,那么就是他分析的那樣了,只要是K,T的公因子的那個就會比較吃香,映射過去的就比較多。所以自然是選擇質數,概率范圍最大了。
    第二個要是2倍小一點點,主要是考慮到數據量的增大,那么,這么選取比較方便
    第三個就純粹說的是allegedly,這樣的實驗效果比較好的緣故了。

    第三個鏈接里面說,單純取余的哈希運算很糟糕。“遠離2^i的質數”、“接近2^i的質數”、“恰好等於2^i”都只不過是“沒有那么差”、“確實很差”和“差到不能再差”的區別。
    個人覺得說得不對,還是根據之前的分析,如果說因為電腦是由二進制來代表數字,就說2的冪次方不適合做哈希表的大小,或者說效果很差,原因不是完全不是因為是二進制,2的冪次方,而是因為他是合數,並且數據本身分布不均勻導致的。
    如果數據分布均勻,其實不管是2的冪次方還是3的冪次方都會是一樣的結果。
    還有就是第四個鏈接里面說的,感覺一部分說的挺對的。
    不過里面也說,首先不能選擇2的倍數,原因是這樣相當於把數字的高位截斷了,如果數字恰巧只有高位部分,那就悲劇了。從某種程度上說,感覺說得挺對的,不過和他自己之前的理論就有點兒矛盾。本來他自己就說選啥都OK,結果現在又說選2的冪次方有問題,這個我不能接受。
    我覺得根本性的問題還是在於他本身是一個質數上。
    因為所謂的高位截斷,其實就是說如果選擇的是m大小的,那么k*m的都會被映射到同一個位中,這是很正常的一件事,因為你不能說計算機選擇什么進制就不能以該數的冪次方來作為哈希表大小。
    當然我承認,單純取余的哈希運算很糟糕,但是作為映射到哈希表中的最后一步填槽運算的時候,其實影響不大了,可能就和鏈接1,2里面說的那樣,只要是個質數就行,只不過實驗效果來看,原理2的冪次方的質數效果稍微好點兒。
    其實可能還有一個理由,有些哈希函數對字符串處理的時候,采用的就是ANSI編碼或者什么編碼方式,這種編碼方式的話,可能弄出來的字符串轉成整數時,出現2的冪次方的可能性比較大,數據分布很不均勻導致的。

    綜上所述:
    1.單純取余哈希算法效果不好的原因主要是因為數據本身分布不均勻導致的,在數據分布均勻的情況下,不管你取的是質數還是合數,對於最后的結果都沒有影響,只要你的數據是平均分布在0-k*m的區間范圍之間的。
    2.不過一個好的哈希函數,就是需要在各種不同的數據分布情況下,其效果是最優的。考慮到這一點,哈希取余算法自然最好是一個質數,因為根據鏈接四滕亦飛里面他推導的公式,可以知道,作為質數,最后求出來的值的值域最大,在0-m-1之間
    3.由2可知,2的冪次方就首先被排除了,不僅是2的冪次方、3的冪次方,4的冪次方……都被排除了。但是,為什么不能取距離2的冪次方的質數、遠離2的冪次方的質數,這一點,我認為是沒有理論依據的,只能說,根據實驗效果來看,遠離2的冪次方的質數效果比較好,所以選擇這個。
    4.上面3點說的都是作為哈希函數的取余算法的效果,然而作為使用哈希表的,不管你之前做了什么哈希函數的映射,到最后一步,存到哈希表中的時候,都會對哈希表的大小進行一個取余的運算,使數據盡量均勻分布在槽里面。由上面說的,如果你之前的哈希函數足夠好,把數據量都均勻分布了,那選啥都沒問題;但為了防止之前的哈希不夠好,自然哈希表的大小開具為一個質數比較OK,而且根據實驗效果來看,開具成一個盡量遠離2的冪次方的哈希表大小比較OK。

如果有說錯的地方歡迎大家批評指正!


免責聲明!

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



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