暴雪公司關於字符串匹配的hash算法

本文轉自 http://blog.csdn.net/shanzhizi/article/details/7736526


暴雪公司有個經典的字符串的hash公式

      先提一個簡單的問題,假如有一個龐大的字符串數組,然後給你一個單獨的字符串,讓你從這個數組中查找是否有這個字符串並找到它,你會怎麼做? 有一個方法最簡單,老老實實從頭查到尾,一個一個比較,直到找到爲止,我想只要學過程序設計的人都能把這樣一個程序作出來,但要是有程序員把這樣的程序交給用戶,我只能用無語來評價,或許它真的能工作,但...也只能如此了。 最合適的算法自然是使用HashTable(哈希表),先介紹介紹其中的基本知識,所謂Hash,一般是一個整數,通過某種算法,可以把一個字符串"壓縮" 成一個整數,這個數稱爲Hash,當然,無論如何,一個32位整數是無法對應回一個字符串的,但在程序中,兩個字符串計算出的Hash值相等的可能非常小,下面看看在MPQ中的Hash算法:

  1. unsigned long HashString(char*lpszFileName, unsigned long dwHashType)   
  2. {   
  3.     unsigned char*key = (unsigned char*)lpszFileName;   
  4.     unsigned long seed1 =0x7FED7FED, seed2 =0xEEEEEEEE;   
  5.     int ch;   
  6.   
  7.     while(*key !=0)   
  8.     {   
  9.         ch = toupper(*key );   
  10.   
  11.         seed1 = cryptTable[(dwHashType <<8) ch] ^ (seed1 seed2);   
  12.         seed2 = ch+ seed1+ seed2 +(seed2 <<5) 3;   
  13.     }   
  14.     return seed1;   
  15. }  

Blizzard的這個算法是非常高效的,被稱爲"One-Way Hash",舉個例子,字符串"unitneutralacritter.grp"通過這個算法得到的結果是0xA26067F3。 是不是把第一個算法改進一下,改成逐個比較字符串的Hash值就可以了呢,答案是,遠遠不夠,要想得到最快的算法,就不能進行逐個的比較,通常是構造一個哈希表(Hash Table,http://blog.csdn.net/shanzhizi)來解決問題,哈希表是一個大數組,這個數組的容量根據程序的要求來定義,例如1024,每一個Hash值通過取模運算 (mod)對應到數組中的一個位置,這樣,只要比較這個字符串的哈希值對的位置又沒有被佔用,就可以得到最後的結果了,想想這是什麼速度?是的,是最快的O(1),現在仔細看看這個算法吧


  1. int GetHashTablePos(char*lpszString, SOMESTRUCTURE *lpTable, int nTableSize)   
  2. {   
  3.     int nHash = HashString(lpszString), nHashPos = nHash % nTableSize;   
  4.   
  5.     if (lpTable[nHashPos].bExists &&!strcmp(lpTable[nHashPos].pString, lpszString))   
  6.         return nHashPos;   
  7.     else   
  8.         return-1; //Error value   
  9. }  

看到此,我想大家都在想一個很嚴重的問題:"假如兩個字符串在哈希表中對應的位置相同怎麼辦?",究竟一個數組容量是有限的,這種可能性很大。解決該問題的方法很多,我首先想到的就是用"鏈表",感謝大學裏學的數據結構教會了這個百試百靈的法寶,我碰到的很多算法都可以轉化成鏈表來解決,只要在哈希表的每個入口掛一個鏈表,保存所有對應的字符串就OK了。 事情到此似乎有了完美的結局,假如是把問題獨自交給我解決,此時我可能就要開始定義數據結構然後寫代碼了。然而Blizzard的程序員使用的方法則是更精妙的方法。基本原理就是:他們在哈希表中不是用一個哈希值而是用三個哈希值來校驗字符串。 中國有句古話"再一再二不能再三再四",看來Blizzard也深得此話的精髓,假如說兩個不同的字符串經過一個哈希算法得到的入口點一致有可能,但用三個不同的哈希算法算出的入口點都一致,那幾乎可以肯定是不可能的事了,這個機率是1:18889465931478580854784,大概是10的 22.3次方分之一,對一個遊戲程序來說足夠安全了。 現在再回到數據結構上,Blizzard使用的哈希表沒有使用鏈表,而採用"順延"的方式來解決問題,看看這個算法:
 

  1. int GetHashTablePos(char*lpszString, MPQHASHTABLE *lpTable, int nTableSize)   
  2. {   
  3.     constint HASH_OFFSET =0, HASH_A =1, HASH_B =2;   
  4.     int nHash = HashString(lpszString, HASH_OFFSET);   
  5.     int nHashA = HashString(lpszString, HASH_A);   
  6.     int nHashB = HashString(lpszString, HASH_B);   
  7.     int nHashStart = nHash % nTableSize, nHashPos = nHashStart;   
  8.       
  9.     while (lpTable[nHashPos].bExists)   
  10.     {   
  11.         if (lpTable[nHashPos].nHashA == nHashA && lpTable[nHashPos].nHashB == nHashB)   
  12.             return nHashPos;   
  13.         else   
  14.             nHashPos = (nHashPos 1) % nTableSize;   
  15.           
  16.         if (nHashPos == nHashStart)   
  17.             break;   
  18.     }   
  19.   
  20.     return-1; //Error value   
  21. }   

1. 計算出字符串的三個哈希值(一個用來確定位置,另外兩個用來校驗)
2. 察看哈希表中的這個位置
3. 哈希表中這個位置爲空嗎?假如爲空,則肯定該字符串不存在,返回
4. 假如存在,則檢查其他兩個哈希值是否也匹配,假如匹配,則表示找到了該字符串,返回
5. 移到下一個位置,假如已經越界,則表示沒有找到,返回
6. 看看是不是又回到了原來的位置,假如是,則返回沒找到 7. 回到3
 

下面用一個靜態數組做一個簡單模擬(沒有處理hash衝突):

  1. #include <stdio.h>   
  2. #define HASH_TABLE_SIZE 13 // 哈希表的大小應是個質數   
  3. struct mapping   
  4. {   
  5.   void *key;   
  6.   void *data;   
  7. } hash_table[HASH_TABLE_SIZE];   
  8.   
  9. unsigned int   
  10. RSHash (char *str)   
  11. {   
  12.   unsigned int b = 378551;   
  13.   unsigned int a = 63689;   
  14.   unsigned int hash =      0  ;   
  15.   
  16.   while (*str)   
  17.     {   
  18.       hash = hash * a + (*str++);   
  19.       a *= b;   
  20.     }   
  21.   return (hash & 0x7FFFFFFF);   
  22. }   
  23.   
  24. int main ()   
  25. {   
  26.   char *str = "we are the world!";   
  27.   char *filename = "myfile.txt";   
  28.   unsigned int hash_offset;   
  29.   // 初始化哈希表   
  30.   memset (hash_table, 0x0, sizeof (hash_table));   
  31.   
  32.   // 將字符串插入哈希表 .   
  33.   hash_offset = RSHash (str) % HASH_TABLE_SIZE;   
  34.   hash_table[hash_offset].key = str;   
  35.   hash_table[hash_offset].data = filename;   
  36.   
  37.   // 查找 str 是否存在於 hash_table.   
  38.   hash_offset = RSHash (str) % HASH_TABLE_SIZE;   
  39.   if (hash_table[hash_offset].key)   
  40.         printf ("string '%s' exists in the file %s./n", str, hash_table[hash_offset].data);   
  41.   else   
  42.         printf ("string '%s' does not exist./n", str);  
  43.   
  44. return 0;  
  45. }   

下面是一個類的封裝:

  1. 代碼  
  2.   
  3. 一、類聲明頭文件  
  4. /////////////////////////////////////////////////////////////////////////////  
  5. // Name:        HashAlgo.h  
  6. // Purpose:     使用魔獸Hash算法,實現索引表的填充和查找功能。  
  7. // Author:      陳相禮  
  8. // Modified by:  
  9. // Created:     07/30/09  
  10. // RCS-ID:      $Id: treetest.h 43021 2009-07-30 16:36:51Z VZ $  
  11. // Copyright:   (C) Copyright 2009, TSong Corporation, All Rights Reserved.  
  12. // Licence:       
  13. /////////////////////////////////////////////////////////////////////////////  
  14. #define MAXFILENAME 255     // 最大文件名長度  
  15. #define MAXTABLELEN 1024    // 默認哈希索引表大小  
  16. //////////////////////////////////////////////////////////////////////////  
  17. // 測試宏定義,正式使用時關閉  
  18. #define DEBUGTEST 1  
  19. //////////////////////////////////////////////////////////////////////////  
  20. // 哈希索引表定義  
  21. typedef struct  
  22. {  
  23.     long nHashA;  
  24.     long nHashB;  
  25.     bool bExists;  
  26.     char test_filename[MAXFILENAME];  
  27.     // ......  
  28. } MPQHASHTABLE;  
  29. //////////////////////////////////////////////////////////////////////////  
  30. // 對哈希索引表的算法進行封裝  
  31. class CHashAlgo  
  32. {  
  33. public:  
  34. #if DEBUGTEST  
  35.     long  testid;   // 測試之用  
  36. #endif  
  37.     CHashAlgo( constlong nTableLength = MAXTABLELEN )// 創建指定大小的哈希索引表,不帶參數的構造函數創建默認大小的哈希索引表  
  38.     {  
  39.         prepareCryptTable();  
  40.         m_tablelength = nTableLength;  
  41.           
  42.         m_HashIndexTable =new MPQHASHTABLE[nTableLength];  
  43.         for ( int i =0; i < nTableLength; i++ )  
  44.         {  
  45.             m_HashIndexTable[i].nHashA =-1;  
  46.             m_HashIndexTable[i].nHashB =-1;  
  47.             m_HashIndexTable[i].bExists =false;  
  48.             m_HashIndexTable[i].test_filename[0] ='\0';  
  49.         }          
  50.     }  
  51.     void prepareCryptTable();                                               // 對哈希索引表預處理  
  52.     unsigned long HashString(char*lpszFileName, unsigned long dwHashType); // 求取哈希值      
  53. long GetHashTablePos( char*lpszString );                               // 得到在定長表中的位置  
  54. bool SetHashTable( char*lpszString );                                  // 將字符串散列到哈希表中  
  55.     unsigned long GetTableLength(void);  
  56.     void SetTableLength( const unsigned long nLength );  
  57.     ~CHashAlgo()  
  58.     {  
  59.         if ( NULL != m_HashIndexTable )  
  60.         {  
  61.             delete []m_HashIndexTable;  
  62.             m_HashIndexTable = NULL;  
  63.             m_tablelength =0;  
  64.         }  
  65.     }  
  66. protected:  
  67. private:  
  68.     unsigned long cryptTable[0x500];  
  69.     unsigned long m_tablelength;    // 哈希索引表長度  
  70.     MPQHASHTABLE *m_HashIndexTable;  
  71. };   
  72. 二、類實現文件  
  73. view plaincopy to clipboardprint?  
  74. /////////////////////////////////////////////////////////////////////////////     
  75. // Name:        HashAlgo.cpp     
  76. // Purpose:     使用魔獸Hash算法,實現索引表的填充和查找功能。     
  77. // Author:      陳相禮     
  78. // Modified by:     
  79. // Created:     07/30/09     
  80. // RCS-ID:      $Id: treetest.h 43021 2009-07-30 16:36:51Z VZ $     
  81. // Copyright:   (C) Copyright 2009, TSong Corporation, All Rights Reserved.     
  82. // Licence:          
  83. /////////////////////////////////////////////////////////////////////////////     
  84.     
  85. #include "windows.h"     
  86. #include "HashAlgo.h"     
  87.     
  88. //////////////////////////////////////////////////////////////////////////     
  89. // 預處理     
  90. void CHashAlgo::prepareCryptTable()     
  91. {      
  92.     unsigned long seed =0x00100001, index1 =0, index2 =0, i;     
  93.     
  94.     for( index1 =0; index1 <0x100; index1++ )     
  95.     {      
  96.         for( index2 = index1, i =0; i <5; i++, index2 +=0x100 )     
  97.         {      
  98.             unsigned long temp1, temp2;     
  99.             seed = (seed *125+3) %0x2AAAAB;     
  100.             temp1 = (seed &0xFFFF) <<0x10;     
  101.             seed = (seed *125+3) %0x2AAAAB;     
  102.             temp2 = (seed &0xFFFF);     
  103.             cryptTable[index2] = ( temp1 | temp2 );      
  104.         }      
  105.     }      
  106. }     
  107.     
  108. //////////////////////////////////////////////////////////////////////////     
  109. // 求取哈希值     
  110. unsigned long CHashAlgo::HashString(char*lpszFileName, unsigned long dwHashType)     
  111. {      
  112.     unsigned char*key = (unsigned char*)lpszFileName;     
  113.     unsigned long seed1 =0x7FED7FED, seed2 =0xEEEEEEEE;     
  114.     int ch;     
  115.     
  116.     while(*key !=0)     
  117.     {      
  118.         ch = toupper(*key++);     
  119.     
  120.         seed1 = cryptTable[(dwHashType <<8) + ch] ^ (seed1 + seed2);     
  121.         seed2 = ch + seed1 + seed2 + (seed2 <<5) +3;      
  122.     }     
  123.     return seed1;      
  124. }     
  125.     
  126. //////////////////////////////////////////////////////////////////////////     
  127. // 得到在定長表中的位置     
  128. long CHashAlgo::GetHashTablePos(char*lpszString)     
  129.     
  130. {      
  131.     const unsigned long HASH_OFFSET =0, HASH_A =1, HASH_B =2;     
  132.     unsigned long nHash = HashString(lpszString, HASH_OFFSET);     
  133.     unsigned long nHashA = HashString(lpszString, HASH_A);     
  134.     unsigned long nHashB = HashString(lpszString, HASH_B);     
  135.     unsigned long nHashStart = nHash % m_tablelength,     
  136.         nHashPos = nHashStart;     
  137.     
  138.     while ( m_HashIndexTable[nHashPos].bExists)     
  139.     {      
  140.         if (m_HashIndexTable[nHashPos].nHashA == nHashA && m_HashIndexTable[nHashPos].nHashB == nHash)      
  141.             return nHashPos;      
  142.         else      
  143.             nHashPos = (nHashPos +1) % m_tablelength;     
  144.     
  145.         if (nHashPos == nHashStart)      
  146.             break;      
  147.     }     
  148.     
  149.     return-1; //沒有找到     
  150. }     
  151. //////////////////////////////////////////////////////////////////////////     
  152. // 通過傳入字符串,將相應的表項散列到索引表相應位置中去     
  153. bool CHashAlgo::SetHashTable( char*lpszString )     
  154. {     
  155.     const unsigned long HASH_OFFSET =0, HASH_A =1, HASH_B =2;     
  156.     unsigned long nHash = HashString(lpszString, HASH_OFFSET);     
  157.     unsigned long nHashA = HashString(lpszString, HASH_A);     
  158.     unsigned long nHashB = HashString(lpszString, HASH_B);     
  159.     unsigned long nHashStart = nHash % m_tablelength,     
  160.         nHashPos = nHashStart;     
  161.     
  162.     while ( m_HashIndexTable[nHashPos].bExists)     
  163.     {      
  164.         nHashPos = (nHashPos +1) % m_tablelength;     
  165.         if (nHashPos == nHashStart)      
  166.         {     
  167.     
  168. #if DEBUGTEST     
  169.             testid =-1;     
  170. #endif  
  171.     
  172.             returnfalse;      
  173.         }     
  174.     }     
  175.     m_HashIndexTable[nHashPos].bExists =true;     
  176.     m_HashIndexTable[nHashPos].nHashA = nHashA;     
  177.     m_HashIndexTable[nHashPos].nHashB = nHash;     
  178.     strcpy( m_HashIndexTable[nHashPos].test_filename, lpszString );     
  179.     
  180. #if DEBUGTEST     
  181.     testid = nHashPos;     
  182. #endif  
  183.     
  184.     returntrue;     
  185. }     
  186.     
  187. //////////////////////////////////////////////////////////////////////////     
  188. // 取得哈希索引表長     
  189. unsigned long CHashAlgo::GetTableLength(void)     
  190. {     
  191.     return m_tablelength;     
  192. }     
  193.     
  194. //////////////////////////////////////////////////////////////////////////     
  195. // 設置哈希索引表長     
  196. void CHashAlgo::SetTableLength( const unsigned long nLength )     
  197. {     
  198.     m_tablelength = nLength;     
  199.     return;     
  200. }    
  201.   
  202. 三、測試主文件  
  203. view plaincopy to clipboardprint?  
  204. /////////////////////////////////////////////////////////////////////////////     
  205. // Name:        DebugMain.cpp     
  206. // Purpose:     測試Hash算法封裝的類,完成索引表的填充和查找功能的測試。     
  207. // Author:      陳相禮     
  208. // Modified by:     
  209. // Created:     07/30/09     
  210. // RCS-ID:      $Id: treetest.h 43021 2009-07-30 16:36:51Z VZ $     
  211. // Copyright:   (C) Copyright 2009, TSong Corporation, All Rights Reserved.     
  212. // Licence:          
  213. /////////////////////////////////////////////////////////////////////////////     
  214.     
  215. //////////////////////////////////////////////////////////////////////////     
  216. // 測試參數設定宏     
  217. #define TESTNUM 32     
  218.     
  219. #include <iostream>     
  220. #include <fstream>     
  221. #include "HashAlgo.h"     
  222.     
  223. usingnamespace std;     
  224.     
  225. //////////////////////////////////////////////////////////////////////////     
  226. // 測試主函數開始     
  227. int main( int argc, char**argv )     
  228. {     
  229.     CHashAlgo hash_test( TESTNUM );     
  230.     
  231.     cout <<"取得初始化散列索引表長爲:"<< hash_test.GetTableLength() << endl;     
  232.     
  233.     bool is_success = hash_test.SetHashTable( "test" );     
  234.     if ( is_success )     
  235.     {     
  236.         cout <<"散列結果一:成功!"<< endl;     
  237.     }     
  238.     else    
  239.     {     
  240.         cout <<"散列結果一:失敗!"<< endl;     
  241.     }     
  242.          
  243.     is_success = hash_test.SetHashTable( "測試" );     
  244.     if ( is_success )     
  245.     {     
  246.         cout <<"散列結果二:成功!"<< endl;     
  247.     }     
  248.     else    
  249.     {     
  250.         cout <<"散列結果二:失敗!"<< endl;     
  251.     }     
  252.     
  253.     long pos = hash_test.GetHashTablePos( "test" );     
  254.     cout <<"查找測試字符串:\"test\" 的散列位置:"<< pos << endl;     
  255.     pos = hash_test.GetHashTablePos( "測試" );     
  256.     cout <<"查找測試字符串:“測試” 的散列位置:"<< pos << endl;     
  257.     
  258.     //////////////////////////////////////////////////////////////////////////     
  259. // 散列測試     
  260. for ( int i =0; i < TESTNUM; i++ )     
  261.     {     
  262.         char buff[32];     
  263.         sprintf(buff, "abcdefg%d.", i);     
  264.         is_success = hash_test.SetHashTable(buff);     
  265.         is_success ? cout << buff <<"散列結果:成功!位置:"<< hash_test.testid << endl : cout << buff <<"散列結果:失敗!"<< endl;           
  266.     }     
  267.     system( "pause" );     
  268.     //////////////////////////////////////////////////////////////////////////     
  269. // 查找測試     
  270. for ( int i =0; i < TESTNUM; i++ )     
  271.     {     
  272.         char buff[32];     
  273.         sprintf(buff, "abcdefg%d.", i);     
  274.         pos = hash_test.GetHashTablePos( buff );     
  275.         pos !=-1?  cout <<"查找測試字符串:"<< buff <<" 的散列位置:"<< pos << endl : cout << buff <<"存在衝突!"<< endl;        
  276.     }     
  277.     
  278.     system( "pause" );     
  279.     return0;     
  280. }  

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