1. 程式人生 > >ELFhash - 優秀的字符串哈希算法

ELFhash - 優秀的字符串哈希算法

right 數據結構 兩個 邏輯 ash style fff 剛才 number

ELFhash - 優秀的字符串哈希算法

2016年10月29日 22:12:37 閱讀數:6440 個人分類: 算法雜論 算法精講 數據結構 所屬專欄: 算法與數據結構 版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/ltyqljhwcm/article/details/52966874

1.字符串哈希:

我們先從字符串哈希說起 在很多的情況下,我們有可能會獲得大量的字符串,每個字符串有可能重復也有可能不重復 C不像Python有字典類型的數據結構,我們沒有辦法吧字符串當做是鍵值來保存,所以說我們需要一種hash函數將每個字符串都盡可能減少沖突的情況下去應設一個唯一的整形數據,方便我們的保存,這裏我們就引入了字符串hash算法 現在,有非常多的字符串hash算法都很優秀,本文主要面對ELFhash算法來表述,相對來說比較的清晰

2.ELFhash

首先我需要聲明,字符串hash算法ELFhash的算法的形成的三列的均勻性我不會證明 根據其他的大牛的描述,ELFhash算法對於長字符串和短字符串都有優良的效率,以下的數據援引劉愛貴大神的實驗數據:

Hash應用中,字符串是最為常見的關鍵字,應用非常普通,現在的程序設計語言中基本上都提供了字符串hash表的支持。字符串hash函數非常多,常見的主要有Simple_hash, RS_hash, JS_hash, PJW_hash, ELF_hash, BKDR_hash, SDBM_hash, DJB_hash, AP_hash, CRC_hash等。它們的C語言實現見後面附錄代碼: hash.h, hash.c。那麽這麽些字符串hash函數,誰好熟非呢?評估hash函數優劣的基準主要有以下兩個指標:

(1) 散列分布性

即桶的使用率backet_usage = (已使用桶數) / (總的桶數),這個比例越高,說明分布性良好,是好的hash設計。

(2) 平均桶長

即avg_backet_len,所有已使用桶的平均長度。理想狀態下這個值應該=1,越小說明沖突發生地越少,是好的hash設計。

hash函數計算一般都非常簡潔,因此在耗費計算時間復雜性方面判別甚微,這裏不作對比。

評估方案設計是這樣的:

(1) 以200M的視頻文件作為輸入源,以4KB的塊為大小計算MD5值,並以此作為hash關鍵字;

(2) 分別應用上面提到的各種字符串hash函數,進行hash散列模擬;

(3) 統計結果,用散列分布性和平均桶長兩個指標進行評估分析。

測試程序見附錄代碼hashtest.c,測試結果如下表所示。從這個結果我們也可以看出,這些字符串hash函數真是不相仲伯,難以決出高低,所以實際應用中可以根據喜好選擇。當然,最好實際測試一下,畢竟應用特點不大相同。其他幾組測試結果也類似,這裏不再給出。

Hash函數 桶數 Hash調用總數 最大桶長 平均桶長 桶使用率%
simple_hash 10240 47198 16 4.63 99.00%
RS_hash 10240 47198 16 4.63 98.91%
JS_hash 10240 47198 15 4.64 98.87%
PJW_hash 10240 47198 16 4.63 99.00%
ELF_hash 10240 47198 16 4.63 99.00%
BKDR_hash 10240 47198 16 4.63 99.00%
SDBM_hash 10240 47198 16 4.63 98.90%
DJB_hash 10240 47198 15 4.64 98.85%
AP_hash 10240 47198 16 4.63 98.96%
CRC_hash 10240 47198 16 4.64 98.77%

所以實際應用中我們可以隨便的選取,本文針對ELFhash

3.原理:

首先,我們在開始之前需要明確幾點 1.unsigned int有4個字節,32個比特位 2.異或操作中0是單位元,任何數與1異或相當於取反 3.unsigned無符號類型的數據右移操作均是執行邏輯右移(左高位自動補0) 4.ELFhash算法的核心在於“影響“ 先附上代碼:
  1. unsigned int ELFhash(char *str)
  2. {
  3. unsigned int hash=0;
  4. unsigned int x=0;
  5. while(*str)
  6. {
  7. hash=(hash<<4)+*str; //1
  8. if((x=hash & 0xf0000000)!=0) //2
  9. {
  10. hash^=(x>>24); //影響5-8位,雜糅一次 3
  11. hash&=~x; //清空高四位 4
  12. }
  13. str++; //5
  14. }
  15. return (hash & 0x7fffffff); //6
  16. }

解釋: 首先我們的hash結果是一個unsigned int類型的數據: 0000 0000 0000 0000 1.hash左移4位,將str插入(一個char有八位)這裏我開始也一直是懷疑的態度,那麽第一個字節的高四位不就亂了嗎 實際上這也是我們的第一次雜糅,我們是故意這麽做的,這裏我們需要註意標記一下,我們在第一個字節的高四位做了第一次雜糅 2.x這裏用0xf0000000獲取了hash的第四個字節的高四位,並用高四位作為掩碼做第二次雜糅 在這裏我們首先聲明一下,因為我們的ELFhash強調的是每個字符都要對最後的結構有影響,所以說我們左移到一定程度是會吞掉最高的四位的,所以說我們要將最高的四位先對串產生影響,再讓他被吞掉,之後的所有的影響都是疊加的,這就是多次的雜糅保證散列均勻,防止出現沖突的大量出現 3.x掩碼右移24位移動到剛才的5-8位哪裏在對5-8位進行第二次雜糅 4.我們定時清空高四位,實際上這部操作我們完全沒有必要,但是算法要求,因為我們下一次的左移會自動吞掉這四位//這裏存疑,不會減少我們的hash的範圍? 5.str遞增,引入下一個字符進行雜糅 6.返回一個缺失了最高符號位的無符號數(為了之後防止用到了有符號的時候造成的溢出)作為最後的hash值

4.Code:

  1. /*#include"iostream"
  2. #include"cstdio"
  3. #include"cstring"
  4. using namespace std;
  5. unsigned int a=0x80;
  6. int main()
  7. {
  8. printf("%d\n",a>>1); //無符號數實行邏輯右移
  9. return 0;
  10. } */
  11. #include"iostream"
  12. #include"cstdio"
  13. #include"cstring"
  14. using namespace std;
  15. unsigned int ELFhash(char *str)
  16. {
  17. unsigned int hash=0;
  18. unsigned int x=0;
  19. while(*str)
  20. {
  21. hash=(hash<<4)+*str;
  22. if((x=hash & 0xf0000000)!=0)
  23. {
  24. hash^=(x>>24); //影響5-8位,雜糅一次
  25. hash&=~x; //清空高四位
  26. }
  27. str++;
  28. }
  29. return (hash & 0x7fffffff);
  30. }
  31. int main()
  32. {
  33. char data[100];
  34. memset(data,0,sizeof(data));
  35. scanf("%s",data);
  36. printf("%d\n",ELFhash(data));
  37. return 0;
  38. }

最後,按照我的思路來看的話,ELFhash最多可以散列的空間的大小是幾個億的數據?如果去掉hash&=~x這一句的話會不會擴大我們hash的範圍,盡可能利用空間,我下星期問問數據結構老師好了!

5.應用:

我們在對內存地址的進行的操作的時候,可以將數據的內存地址進行哈希 因為每個數據的內存地址都是唯一的,所以我們只需要一步獲取內存地址的十六進制的表示就可以了 語句是
sprintf(data,"%0x",&now_data);
第一個data保存我們的保留字符串的內存空間(字符串數組) 中間的是保存的進制的形式 最後是我們的要取地址的內存空間
利用這種思路,我們可以很清晰明了的對鏈表相交的問題構建一種新的解法,我們采用哈希我們的內存空間就可以了,可以再O(n)中完成查找

ELFhash - 優秀的字符串哈希算法