21Hash演算法以及暴雪Hash

2021-07-03 06:05:53 字數 3372 閱讀 8358

一:雜湊表簡介

雜湊表是一種查詢效率極高的資料結構,理想情況下雜湊表插入和查詢操作的時間複雜度均為o(1),任何乙個資料項可以在乙個與雜湊表長度無關的時間內計算出乙個雜湊值(key),然後在常量時間內定位到乙個桶(術語bucket,表示雜湊表中的乙個位置)。當然這是理想情況下,因為任何雜湊表的長度都是有限的,所以一定存在不同的資料項具有相同雜湊值的情況,此時不同資料項被定為到同乙個桶,稱為碰撞(collision)。

雜湊表的實現需要解決碰撞問題,碰撞解決大體有兩種思路:

第一種是根據某種原則將被碰撞資料定為到其它桶,例如線性探測——如果資料在插入時發生了碰撞,則順序查詢這個桶後面的桶,將其放入第乙個沒有被使用的桶;

第二種策略是每個桶不是乙個只能容納單個資料項的位置,而是乙個可容納多個資料的資料結構(例如鍊錶或紅黑樹),所有碰撞的資料以某種資料結構的形式組織起來。

不論使用了哪種碰撞解決策略,都導致插入和查詢操作的時間複雜度不再是o(1)。以查詢為例,不能通過key定位到桶就結束,必須還要比較原始資料項是否相等,如果不相等,則要使用與插入相同的演算法繼續查詢,直到找到匹配的值或確認資料不在雜湊表中。

使用單鏈表解決碰撞的雜湊表,平均查詢複雜度為o(l),其中l為桶鍊錶的平均長度;而最壞複雜度為o(n),此時所有資料全部碰撞,雜湊表退化成單鏈表。如下圖:

二:暴雪的hash演算法(mpq)

由乙個簡單的問題逐步入手:有乙個龐大的字串陣列,陣列元素就是字串,然後給定乙個單獨的字串,讓你從這個陣列中查詢是否有這個字串並找到它,你會怎麼做?

有乙個方法最簡單,老老實實從頭查到尾,乙個乙個比較,直到找到為止,這樣做的效率極低。

最合適的演算法自然是使用hashtable(雜湊表),可以把乙個字串"壓縮" 成乙個整數。在暴雪的hash演算法中,兩個字串計算出的hash值相等的可能非常小,下面看看在mpq中的hash演算法:

1:函式preparecrypttable生成乙個長度為0x500(合10進製數:1280)的crypttable[0x500]

void preparecrypttable()  

}   

}   

2:函式hashstring計算字串lpszfilename的hash值,其中dwhashtype 為hash的型別。

unsigned long hashstring(const char *lpszkeyname, unsigned long dwhashtype )    

return seed1;  

}  blizzard的這個演算法是非常高效的,被稱為"one-wayhash"( 即通過hash值反推字串幾乎是不可能的)。舉個例子,字串"unitneutralacritter.grp"通過這個演算法得到的結果是0xa26067f3。

然後是構造乙個雜湊表來解決問題,雜湊表是乙個大陣列,這個陣列的容量根據程式的要求來定義,例如1024。

每乙個hash值通過取模運算 (mod) 對應到陣列中的乙個位置,這樣,只要比較這個字串的雜湊值對應的位置有沒有被占用,就可以得到最後的結果了,

#if 0

想想這是什麼速度?是的,是最快的o(1),現在仔細看看這個演算法吧:

typedef struct  

somestructrue;  

3:函式gethashtablepos在hash表中查詢是否存在目標字串,有則返回要查詢字串的hash值,否則,return -1.

int gethashtablepos( char *lpszstring, somestructure *lptable )   

else  

}  #endif

看到此,我想大家都在想乙個很嚴重的問題:如果兩個字串在雜湊表中對應的位置相同怎麼辦?畢竟乙個陣列容量是有限的,這種可能性很大。

blizzard

的程式設計師使用精妙的方法。基本原理就是:他們在雜湊表中不是用乙個雜湊值而是用三個雜湊值來校驗字串。

mpq

使用的雜湊表的格式與正常的雜湊表有一些不同。它沒有把實際的檔名儲存在表中用於驗證,實際上它根本就沒有儲存檔名。而是使用了3種不同的雜湊:乙個用於雜湊表的下標,兩個用於驗證。這兩個驗證雜湊替代了實際檔名。

假如說兩個不同的字串經過乙個雜湊演算法得到的入口點一致有可能,但用三個不同的雜湊演算法算出的入口點都一致,那幾乎可以肯定是不可能的事了。當然了,這樣仍然會出現2個不同的檔名雜湊到3個同樣的雜湊。但是這種情況發生的概率平均是:1:18889465931478580854784,這個概率對於任何人來說應該都是足夠小的。現在再回到資料結構上,blizzard使用的雜湊表沒有使用鍊錶,而採用"順延"的方式來解決問題。

4:函式gethashtablepos中,lpszstring 為要在hash表中查詢的字串;lptable 為儲存字串hash值的hash表;ntablesize為hash表的長度: 

int gethashtablepos( char *lpszstring, mpqhashtable *lptable, int ntablesize )     

else     

if (nhashpos == nhashstart)  

break;  

}  return -1;  

}  

上述程式解釋:

1計算出字串的三個雜湊值(乙個用來確定位置,另外兩個用來校驗)

2察看雜湊表中的這個位置

3雜湊表中這個位置為空嗎?如果為空,則肯定該字串不存在,返回-1。

4如果存在,則檢查其他兩個雜湊值是否也匹配,如果匹配,則表示找到了該字串,返回其hash值。

5移到下乙個位置,如果已經移到了表的末尾,則反繞到表的開始位置起繼續查詢 

6看看是不是又回到了原來的位置,如果是,則返回沒找到

7回到3。

三:雜湊表大小

雜湊表的陣列是定長的,如果太大,則浪費,如果太小,體現不出效率。合適的陣列大小是雜湊表的效能的關鍵。

雜湊表的尺寸最好是乙個質數。當然,根據不同的資料量,會有不同的雜湊表的大小。對於資料量時多時少的應用,最好的設計是使用動態可變尺寸的雜湊表,那麼如果你發現雜湊表尺寸太小了,比如其中的元素是雜湊表尺寸的2倍時,我們就需要擴大雜湊表尺寸,一般是擴大一倍。

下面是雜湊表尺寸大小的可能取值:

17, 37, 79, 163, 331, 673, 1361, 2729, 5471,10949, 21911, 43853, 87719, 175447, 350899, 701819, 1403641, 2807303, 5614657, 11229331,22458671, 44917381, 89834777, 179669557, 359339171, 718678369, 1437356741, 2147483647

(

hash表 hash演算法

概念 雜湊表 hash table。也叫雜湊表 是依據關鍵碼值 key value 而直接進行訪問的 資料結構。也就是說,它通過把關鍵碼值對映到表中乙個位置來訪問記錄,以加快查詢的速度。這個對映函式叫做雜湊函式,存放記錄的陣列叫做雜湊表。給定表m,存在函式f key 對隨意給定的keyword值ke...

hash位址 Hash演算法基礎

hash,一般翻譯做 雜湊 也有直接音譯為 雜湊 的,就是把任意長度的輸入,通過雜湊演算法,變換成固定長度的輸出,該輸出就是雜湊值。這種轉換是一種壓縮對映,也就是,雜湊值的空間通常遠小於輸入的空間,不同的輸入可能會雜湊成相同的輸出,所以不可能從雜湊值來唯一的確定輸入值。簡單的說就是一種將任意長度的訊...

Hash演算法與Hash碰撞

什麼是hash演算法。雜湊函式 英語 hash function 又稱雜湊演算法 雜湊函式,是一種從任何一種資料中建立小的數字 指紋 的方法。雜湊函式把訊息或資料壓縮成摘要,使得資料量變小,將資料的格式固定下來。該函式將資料打亂混合,重新建立乙個叫做雜湊值 hash values,hash code...