打造最快的Hash表

來源:互聯網
上載者:User

先提一個簡單的問題,如果有一個龐大的字串數組,然後給你一個單獨的字串,讓你從這個數組中尋找是否有這個字串並找到它,你會怎麼做?

有一個方法最簡單,老老實實從頭查到尾,一個一個比較,直到找到為止,我想只要學過程式設計的人都能把這樣一個程式作出來,但要是有程式員把這樣的程式交給使用者,我只能用無語來評價,或許它真的能工作,但...也只能如此了。

最合適的演算法自然是使用HashTable(雜湊表),先介紹介紹其中的基本知識,所謂Hash,一般是一個整數,通過某種演算法,可以把一個字串"壓縮" 成一個整數,這個數稱為Hash,當然,無論如何,一個32位整數是無法對應回一個字串的,但在程式中,兩個字串計算出的Hash值相等的可能非常小,下面看看在MPQ中的Hash演算法

unsigned long HashString(char *lpszFileName, unsigned long dwHashType)

unsigned char *key = (unsigned char *)lpszFileName;
unsigned long seed1 = 0x7FED7FED, seed2 = 0xEEEEEEEE;
int ch;

while(*key != 0)

  ch = toupper(*key++);

seed1 = cryptTable[(dwHashType << 8) + ch] ^ (seed1 + seed2);
seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3; 
}
return seed1; 

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

int nHash = HashString(lpszString), nHashPos = nHash % nTableSize;

if (lpTable[nHashPos].bExists && !strcmp(lpTable[nHashPos].pString, lpszString)) 
  return nHashPos; 
else 
  return -1; //Error value 

看到此,我想大家都在想一個很嚴重的問題:"如果兩個字串在雜湊表中對應的位置相同怎麼辦?",畢竟一個數組容量是有限的,這種可能性很大。解決該問題的方法很多,我首先想到的就是用"鏈表",感謝大學裡學的資料結構教會了這個百試百靈的法寶,我遇到的很多演算法都可以轉化成鏈表來解決,只要在雜湊表的每個入口掛一個鏈表,儲存所有對應的字串就OK了。

事情到此似乎有了完美的結局,如果是把問題獨自交給我解決,此時我可能就要開始定義資料結構然後寫代碼了。然而Blizzard的程式員使用的方法則是更精妙的方法。基本原理就是:他們在雜湊表中不是用一個雜湊值而是用三個雜湊值來校正字串。

中國有句古話"再一再二不能再三再四",看來Blizzard也深得此話的精髓,如果說兩個不同的字串經過一個雜湊演算法得到的進入點一致有可能,但用三個不同的雜湊演算法算出的進入點都一致,那幾乎可以肯定是不可能的事了,這個幾率是1:18889465931478580854784,大概是10的 22.3次方分之一,對一個遊戲程式來說足夠安全了。

現在再回到資料結構上,Blizzard使用的雜湊表沒有使用鏈表,而採用"順延"的方式來解決問題,看看這個演算法:
int GetHashTablePos(char *lpszString, MPQHASHTABLE *lpTable, int nTableSize)

const int HASH_OFFSET = 0, HASH_A = 1, HASH_B = 2;
int nHash = HashString(lpszString, HASH_OFFSET);
int nHashA = HashString(lpszString, HASH_A);
int nHashB = HashString(lpszString, HASH_B);
int nHashStart = nHash % nTableSize, nHashPos = nHashStart;

while (lpTable[nHashPos].bExists)

  if (lpTable[nHashPos].nHashA == nHashA && lpTable[nHashPos].nHashB == nHashB) 
   return nHashPos; 
  else 
   nHashPos = (nHashPos + 1) % nTableSize;
  
  if (nHashPos == nHashStart) 
   break; 
}

return -1; //Error value 

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

怎麼樣,很簡單的演算法吧,但確實是天才的idea, 其實最優秀的演算法往往是簡單有效演算法。

 

From:http://www.cppblog.com/kyelin/archive/2009/03/25/30506.html

 

附:魔獸雜湊演算法封裝和測試

http://blog.csdn.net/eaglewood2005/archive/2009/07/30/4394583.aspx

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.