数据结构和算法——散列函数的构造方法(直接定址法、除留余数法、数字分析法、折叠法、平方取中法、ASCII码加和法、前三字符移位法)

目录

数字关键词的散列函数构造

直接定址法

除留余数法

数字分析法

折叠法

平方取中法

字符关键词的散列函数构造

ASCII码加和法

前三字符移位法

移位法


数字关键词的散列函数构造

一个“好”的散列函数一般应考虑下列两个因素

  1. 计算简单,以便提高转换速度;
  2. 关键词对应的地址空间分布均匀,以尽量减少冲突。

直接定址法

取关键词的某个线性函数值为散列地址,即h(key)=a\times key+b(a、b为常数)。

例如以年份(1990-2011)为关键词,那么就可以将散列函数构造成h(key)=key-1990

地址h(key)出生年份(key)人数(attribute)
019901285万
119911281万
219921280万
..................
1020001250万
..................
2120111180万

除留余数法

散列函数为:h(key)=key\: \: mod\: \: p

例如上篇里提到的:

地址012345678910111213141516
关键词34182202374227113015

这里的散列函数就为:h(key) = key % 17

  • 此处p = TableSize = 17
  • 一般地,p取素数

选择素数p可以避免余数之间的相关性,降低哈希冲突的概率。如果选择合数作为模数,可能存在公因数,增加了冲突的概率。

数字分析法

分析数字关键字在各位上的变化情况,取比较随机的位作为散列地址。

比如:取11位手机号码key的后4位作为地址,则散列函数为:h(key)=atoi(key+7)(char *key)

即,手机号码为12345678910,其地址就为“8910”。

例子

关键词key是18位的身份证号码:

123456789101112131415161718
330106199010080419

区(县)下

属辖区编号

(出生)年份月份日期该辖区中的序号校验

根据分析,发现变化比较大的就是我们标红的六位,所以我们以他们六位来设计散列函数:

\begin{matrix} h_1(key)=(key[6]-'0')\times 10^4+(key[10]-'0')\times 10^3+\: \: \: \: \: \: \: \\ (key[14]-'0')\times 10^2+(key[16]-'0')\times 10+(key[17]-'0') \end{matrix}

当key[18] = ‘X’时,h(key)= h_1(key)\times 10+10

当key[18]为‘0’~‘9’时,h(key)=h_1(key)\times 10+key[18]-'0'

(以十进制来计算地址,所以最高位为10的5次方)

 

折叠法

把关键词分隔成位数相同的几个部分,然后叠加。

比如:56793542,拆分成三部分

\begin{matrix} \: \: \: \: \: \, 542\\ \: \: \: \: \:\, 793\\ +\: \: 056\\ \overline{\: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: }\\ \: \: \: 1391 \end{matrix},取后三位391,所以h(56793542) = 391

平方取中法

将一个数平方之后再取中间位数的值。

比如:56793542

\begin{matrix} \: \: \: \: \: \, \: \: \: \: 56793542\\ \: \: \: \: \:\,\times \: \: 56793542 \: \: \\ \overline{\: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: }\\ \: \: \: 322550\mathbf{641}2905764 \end{matrix}

所以h(56793542)=641

字符关键词的散列函数构造

ASCII码加和法

对字符型关键词key定义散列函数如下:

h(key)=(\sum key[i])\:\: \: mod\:\: \: TableSize

但是这种方法冲突比较严重,a3(97+3)、b2(98+2)、c1(99+1)的ASCII码值加起来都等于100,eat(101+97+116)和tea(116+101+97)的ASCII码值加起来都等于314.

进行一下简单的改进

前三字符移位法

h(key)=(key[0]\times27^2+key[1]\times 27+key[2])\: \: \: mod\: \: \: TableSize

(这里用27进制计算地址,原因是包括空格在内的单个字符共有27个)

但是这种方法仍然容易冲突:string、street、strong、structure等等;

而且会造成空间浪费,理论上来说,前三字符所有可能性的组合为26^3种,但经过统计得到,实际中的组合约3000种,3000/26^3\approx 0.3,即空间利用率大约30%,浪费了70%的空间。

移位法

涉及关键词所有n个字符,并且分布得很好;

h(key)=(\overset{n-1}{\underset{i=0}{\sum}}key[n-i-1]\times 32^i)\: \: mod\: \: TableSize

例如:h(“abcde”) = ‘a’ * 32^4 + ‘b’ * 32^3  + ‘c’ * 32^2  + ‘d’ * 32 + ‘e’。

用这个计算方法需要乘10次,我们看一下比较快的计算方法:

h(“abcde”) = (((‘a’ * 32 + b) * 32 + c) * 32 + d ) * 32 + e,

用这种方法,在计算时就只需要乘4次

还有没有更巧妙的办法呢?

我们发现32是2的5次方,于是可以通过移位,也就是将一个数往左二进制移位五次,就相当于*32.

于是有:

Index Hash(const char *Key, int TableSize)
{
    unsigned int h = 0; /* 散列函数值,初始化为0  */
    while( *Key != '\0')
    {
        h = (h << 5) + *Key++;
    }
    return h % TableSize;
}

end


学习自:MOOC数据结构——陈越、何钦铭

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值