1. 程式人生 > >雜湊演算法(Hash Algorithm)初探

雜湊演算法(Hash Algorithm)初探

不約而同的,幾乎所有的流行的hash map都採用了DJB hash function,俗稱“Times33”演算法。

Perl、Berkeley DB 、Apache、MFC、STL 等等。

times33的演算法也很簡單,就是不斷的乘33。nHash = nHash*33 + *key++;

我沒找到什麼理論可以說明這種演算法的合理性,據說只是通過測試和實踐發現這個演算法是比較好的。如果有哪位能夠提供這方面的資料,不勝感激。

我把times33和一些其他雜湊演算法做過比較,times33確實比我找到的其他雜湊演算法都更快。另外,有人說times33對英文字母效率比較好,處理中文的時候效率就比較低;我對此進行了一些測試,發現times33在處理ascii和中文的時候,效能差異在千分之三以下,我認為這是正常的誤差。



《打造最快的Hash表(和Blizzard的對話)》http://blog.csdn.net/zeronecpp/archive/2005/04/11/342756.aspx
這是在別人的blog上看到的一篇文章,講述blizzard如何改良hash表的。在上述雜湊演算法裡面有一段 “seed2 + (seed2 << 5)” 相當於乘以33,其實可以看作是times33演算法的變種。我對blizzard這種實現方法的效率存有懷疑。

上述blizzard的雜湊演算法的核心如下(我給cryptTable賦了最簡單的值,而且把dwHashType設為了1):

inline UINT CMyMap::HashKey(LPCTSTR key) const
{
    int dwHashType = 1;
    unsigned long seed1 = 0x7FED7FED, seed2 = 0xEEEEEEEE;
    int ch;
    while(*key != 0)
    {
        ch = toupper(*key++);
       
        //seed1 = cryptTable[(dwHashType << 8) + ch] ^ (seed1 + seed2);
        seed1 = ((dwHashType << 8) + ch) ^ (seed1 + seed2);
        seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3;
    }
    return seed1;
}

我進行了一下測試,發現blizzard的雜湊演算法,分佈不如經典的times33演算法。它的分佈如下:elements=10000, good=4293 bad2=1786 bad3=528 bad4=109 vbad=22
而經典times33演算法的分佈是:elements=10000, good=4443 bad2=1775 bad3=501 bad4=107 vbad=15
說明:這是我測試程式的輸出,測試的時候,我通過InitHashTable()把bucket個數設為了12007。輸出中的elements表示雜湊表中一共存放了多少個元素,good表示“只有一個元素”的bucket個數,bad2表示“有兩個元素”的bucket個數,bad3表示“有三個元素”的bucket個數,vbad表示“有五個或者五個以上元素”的bucket個數。

經典times33演算法如下:
inline UINT CMyMap::HashKey(LPCTSTR key) const
{
    UINT nHash = 0;
    while (*key)
        nHash = (nHash<<5) + nHash + *key++;
    return nHash;
}
從程式碼可以很明顯的看出,blizzard的這個hash演算法的計算工作量也要比經典的times33演算法大很多。

我的理解是:這是為了讓讓同一個字串,可以根據dwHashType 的不同而計算出不同的獨立的hash值。為了實現這個目的,blizzard的這個hash演算法在效能上已經付出了一些代價。

//
// 以上是對hash演算法的比較
/////////////////////////////////////////
// 以下是對hash表整體結構的比較
//

另外,blizzard這個演算法本質上還是把資料放在hash bucket裡面,也同樣是在每個hash bucket裡面有一個list佇列。
只不過一般的hash表,在找到hash bucket之後,就逐個的直接比較element;而blizzard的這個hash表,則是用“額外的兩個hash值的比較”來代替element的直接比較。孰優孰劣要看具體的應用環境。
考慮到計算三次hash值的工作量,我覺得如果設定一個合適的hash bucket count,blizzard的做法可能還要更慢。
上面我做的hash分佈測試已經表明,當hash bucket count比elements大20%以上的時候,查詢一個element的strcmp呼叫次數大約是(4443*1+1175*2*1.5+501*3*2+107*4*2.5+15*5*3)/10000=1.2269次,大約是1.2次。(4443個bucket只有一個element,因此一次strcmp就可以確認了。有1175個bucket有兩個元素,平均要1.5次strcmp才能找到它。以此類推。)
做1.2次strcmp()和做2次HashKey()相信大家都知道誰比較耗時了。


看來,這個所謂”最快的hash表“似乎有點名不副實呢?還是另有玄機我沒看透?

所謂"One-way hash"其實就是不可逆hash,主要是用來加密用的,和速度快不快沒什麼關係。實際上"One-way hash"為了達到不可逆的目的,通常總是要更慢一些。blizzard是我很喜歡的公司,我也是暴雪的鐵桿fans,不過這次似乎有人誇暴雪誇錯方向了:)

在google上搜索“hash Algorithm”可以搜到很多有趣的東西。
http://www.partow.net/programming/hashfunctions/ 是一篇很有趣的文章。