数据结构笔记(六)——散列(Hash Table)之散列函数(1)

散列表(hash table)的实现叫做散列(hashing)。这是以常数平均时间O(1)进行插入、删除和查找的技术。散列表没有顺序,需要元素间排序信息的操作,如findMin、findMax不会得到有效支持(就是这东西不是这么用的,你可以实现,但效率不会很高)。理想情况下,散列表是一个包含关键字的具有固定大小的数组,数组大小一般被视为散列表的一部分。数据通过散列函数简单的计算映射到数组适当的位置上,理想情况下当然是每个关键字对应一个位置,但这是不可能的。关键字那么多,数组大小总是有限的,总会有那么两个关键字被映射到同一个位置。因此我们放宽要求,希望散列函数简单,而且使关键字分布得比较均匀。一般关键字有整数和字符串,对应就有不同的散列函数,这里介绍几种散列函数:

关键字为整数:

1、取余法

用关键字对M取余作为散列地址,散列函数为hash(x)=x mod M,M一般为数组大小。这里的数组大小需要仔细考虑,设想如果M=10,而关键字都以0结尾,他们将会全部散列到0这个位置。一般取M为素数是比较好的选择。当然对于取余法还有很多可以改进的地方,比如对x进行线性运算(a*x+b)再取余来增大每个x的间隔,x乘一个小数对结果的小数部分进行其他操作等,散列函数还是要根据实际需求来选择。

2、平方取中法

先对x求平方,截取结果的中间几位作为散列函数值。这样做的好处在于中间几位数与关键字每一数位都有关,受关键字的影响较大。理解一下:

一个数的平方可以拆成这个数进行多次移位相加的结果,比如56,二进制是111000,111000*111000=111000*(2^3+2^4+2^5),即111000分别向右移3,4,5位,取中间几位利用到了56的各个数位。

数据结构笔记(六)——散列(Hash Table)之散列函数(1)_第1张图片

关键字为字符串:

1、把字符串的ASCII码值加起来。这种方法简单快速,不过如果表很大,将无法均匀的分配关键字。假设tablesize=10007,关键字最多8个字符长,char的值最大为127,那么散列函数的取值范围是0~127*8=1016,不是均匀分配。

Index Hash1( const char *Key, int TableSize )
        {
            unsigned int HashVal = 0;

            while( *Key != '\0' )
                HashVal += *Key++;

            return HashVal % TableSize;
        }

2、hash(key)=key[0]+27*key[1]+729*key[2]。假设关键字key至少含有2个字符外加null结束符,27表示26个英文字母加空格,729=27^2,函数仅考察前三个字符。如果字符是随机的,那么3个字符具有26^3=17576种可能组合,表长为10007时也可以均匀分配。但是实际上3个字母的组合不是随机的,只有2851种组合,所以这种方法实际上也不可行。

Index Hash2( const char *Key, int TableSize )
{
    return ( Key[ 0 ] + 27 * Key[ 1 ] + 729 * Key[ 2 ] )
        % TableSize;
}

3、第三种方法是采用关键字中所有的字符,进行和第二种方法类似的计算,只不过稍加改动。

hash(key)=\sum _{i=0}^{KeySize-1}key[KeySize-i-1]*32^i 

利用honer法则(秦九韶公式)可以简化运算,如:

f(k)=k_1+32*k_2+32^2*k_3=k_1+32*(k_2+(32*k_3)).

相比方法二,这里采用32而不是27,是因为32=2^5,方便移位进行乘法运算。如果字符过长,这个方法计算会花费过多的时间,而且字符左移会移出,实际可以选择一个合适的长度。

Index Hash3( const char *Key, int TableSize )
{
    unsigned int HashVal = 0;

    while( *Key != '\0' )
        HashVal = ( HashVal << 5 ) + *Key++;

    return HashVal % TableSize;
}

我们已经尽可能的让关键字分布均匀,但当两个关键字不可避免的被映射到同一位置时,我们要解决这种冲突,可以采用分离链接法和开放定址法,下一篇写吧。

你可能感兴趣的:(数据结构)