Hash學習(2)-Hash函數

            一個好的hash函數一般具有以下兩個特點:第一,速度快,第二,能夠將散列鍵均勻的分佈在整個表中,保證不會產生聚集。通常,hash函數具有如下形式:

hash-key = calculated-key % tablesize

上一節主要討論了一下tablesize,爲了提高散列鍵的離散程度,tablesize通常取素數。一般而言,沒有絕對好的hash函數,hash函數的好壞很大程度上依賴於輸入鍵的結構,人們討論的最多的一般都是輸入鍵爲普通字符串的情況。這裏也以字符串爲例討論如何一步步優化hash函數。

         當鍵是字符串時,一種選擇策略是簡單的將字符串中每個字符的ASCII碼加起來,代碼如下:   

unsigned int hash(const char *key, unsigned int tableSize)
{
	unsigned int hashVal;

	while(*key != '\0')
		hashVal += *key++;

	return (hashVal % tableSize);
}
         上面的hash函數實現簡單而且能夠很快的算出答案,不過,如果表很大,則函數就不能很好的分配鍵,例如,設tableSize=10949,並設所有的鍵至多8個字符長,由於ASCII字符的值最多是127,因此hash函數只能在0~1016之間取值,其中1016爲127*8,顯然這不是一種均勻的分配。

         下面的hash函數對針對上面的缺點進行了改進。

unsigned int hash(const char *key, unsigned int tableSize)
{
	return (key[0] + 27*key[1] + 729*key[2]) % tableSize;
}
         這個hash函數假設key至少有3個字符。值27表示英文字母表的字母個數外加一個空格,而729是27的平方。雖然該函數只考察了前三個字符,但是,假如字符出現的概率是隨機的,而表的大小還是10949,那麼我們就會得到一個合理的均衡分佈。可是,英文不是隨機的。雖然3個字符有26*26*26=17567種可能的組合,但查驗詞彙量足夠大的聯機詞典卻揭示出:3個字母的不同組合數實際上只有2851種。即使這些組合沒有衝突,也不過只有表的28%被真正散列到。因此,雖然容易計算,但是當hash表足夠大的時候,這個函數還是不合適。

         針對以上缺點,進一步改進:

unsigned int hash(const char *key,unsigned int tableSize)
{
	unsigned int hashVal;

	while(*key != '\0')
		hashVal = (hashVal << 5) + *key++;

	return (hashVal % tableSize);
}
         這個hash函數涉及鍵中的所有字符,並且一般可以分佈的很好,它計算了字符串的如下值:


在計算該值時,利用了Horner法則,例如計算 hash=a+32b+32*32c 的另一種方式是藉助公式:hash=((c)*32+b)*32+a。Horner法則將其擴展到用於n次多項式。該算法通過將乘法運算轉換爲位運算保證了hash函數快速的特點。

       下面給出實際字符串hash應用中使用的很廣的一個hash函數:ELFhash

unsigned long ELfHash(const unsigned char * key)
{
	unsigned long h = 0, g;

	while(*key)
	{
		h = (h << 4) + *key++;//把h左移4位加上該字符賦給h
		if(g = h & 0xF0000000)//取h的高四位賦給g
			h ^= g >> 24;//如果g不爲0,讓h和g的高八位異或再賦給h

		h &= ~g;//對g取反並與h相與賦給h
	}
	return h;
}



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