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;
}



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