哈希表(二)

来源:互联网 发布:linux 添加epel源 编辑:程序博客网 时间:2024/06/06 13:06

电话簿
设计一种数据结构来存储联系方式:包含人名和电话号码
这种数据结构应该可是快速实现以下操作:

添加和删除联系人
使用人名查找电话号码
通过电话号码确定打电话的人的姓名

要实现上述操作,我们需要实现两种映射关系:

电话号码——>名字
名字——>电话号码

首先,实现由电话号码到名字的映射

方法:
1.直接寻址

把电话号码转换成整数.例如:int(123-45-67) = 1234567
创建一个大小为10^L的名字数组,其中L为允许最大电话号码长度
在数组name[int(p)]存储和电话号码p相联系的名字
如果电话号码没有对应的联系人,那么name[int(p)] = N/A

方法特点分析:

操作的时间复杂度为O(1)
存储空间使用:O(10^L),L是电话号码的最大长度.
当电话号码的长度大于或等于12的时候,至少需要10^12=1 TB的存储空间,这是不适用的

2.链接

选择基数为m的哈希函数h
创建大小为m的数组name
在name数组的每个格存储链表
链表name[h(int(p))]存储的是电话号码p对应的名字

参数

n——要存储电话号码的个数
m——哈希函数的基数
c——最长链表的长度
存储空间:O(n+m)
a = n/m——负载因子
操作的时间复杂度O(c + 1)
其中m和c应该尽可能小

哈希函数的设置

1.前三个数字

选择基数m=1000,由电话号码映射到名字
哈希函数:取电话号码的前三个数字.例如:h(800-123-45-67) = 800
问题:存储大量同一个地区的电话号码时,c会非常大

2.最后三个数字

选择m=1000
哈希函数:取电话号码的最后三位数字.例如h(800-123-45-67) = 567
问题:如果有大量的电话号码的最后三位数字相同,c也会很长

3.随机值

选择m=1000
哈希函数:在0-999中随机取值
哈希值均匀分布
相同电话号码再次调用哈希函数时会的到不同的值,也就是不能从哈希值中得到任何有价值的信息
哈希函数必须时确定的

优秀哈希函数的特性

确定性
计算速度快
不同的键值对应不同的哈希值
不存在冲突

定理:如果键的数目很多(|U| >> m),对于任何哈希函数都会存在大量的冲突

通用系列哈希函数
理想情况

使用随机性
定义一系列的哈希函数
从这一系列的哈希函数里随机选择一个哈希函数

定义:假设U是普遍通用的——所有可能键值的集合,满足对于任意两个键值,x,y均属于U,且x != y冲突的概率为:Pr[h(x) = h(y)] <=1/m 的哈希函数集合H = {h:U ——> {0,1,…,m-1}}成为通用系列哈希函数.

随机性的工作

h(x) = random({0,1,2,…,m-1})发生冲突的概率是:1/m
具有随机性,不能直接使用
在集合H里的所有哈希函数具有确定性
从集合H中选择一个随机函数h
在算法中使用已选择的确定的函数

时间复杂度定理:若h是从一个通用系列哈希函数集中随机选择的函数,那么它对应的最长链的平均长度c为O(1+a),当a=n/m是哈希表的负载因子

推论:若h是从通用系列哈希函数集中随机选择的函数,那么哈希表对应的操作的时间复杂度为O(1+a)

哈希表大小的选择

使用基数m的大小控制存储空间的使用
理想的,负载因子0.5

loadFactor <---- T.numberOfKeys/T.sizeif loadFactor > 0.9    create Tnew of size 2 * T.size    选择基数为Tnew.size的新哈希函数hnew    for each object o in T:        insert o in Tnew using hnew    T <---- Tnew, h <--- hnew

与动态数组类似,单个重新求哈希表的时间复杂度为:O(n),但是哈希表的的每一个操作的平均时间复杂度仍为O(1),因为重新hash计算实际很少

整数哈希计算

假设电话号码的长度为7,例如:148-25-67
把电话号码转化为范围为:0-10^7-1=9999999的整数.例如:148-25-76 ——>1472576
选择一个大于10^7的质数p
确定哈希表的大小.例如:m=1000

定理:对于所有a,b:1<=a<=p-1,0<=b<=p-1,Hp = {h(x)=((ax + b)mod p)mod m}是一个通用系列哈希函数集

一旦确定了a和b,也就从这个通用系列哈希函数集中确定了一个哈希函数.其中x是键值,就是我们想要计算的哈希值的整数

例子,求电话号码的哈希值
a = 34, b = 2, p = 10000019, x = 1482567
所以, h(x) = ((34 * 1482567 + 2)mod 10000019)mod 1000 = 491

一般整数哈希值计算情况

确定电话号码的最大长度
把电话号码转化成范围为0-10^L-1的整数
选择大于10^L的质数p
选择哈希表的大小m
从通用系列哈希函数集中随机选择一个哈希函数(随机选择a,b)

字符串的哈希值计算
通过名字查找电话号码

我们需要实现名字到电话号码的映射
同样可以使用链接实现
需要一个由名字定义的哈希函数
对字符串中的每一个字符进行哈希计算

定义:|S|表示字符串S的长度

给定一个字符串,计算他的哈希值:

S=S[0],S[1],…,S[|S|-1],其中S[i]表示单一字符
应该调用哈希函数计算所有字符对应的哈希值
否则,可能会产生很多的冲突:例如,如果不对a[0]计算哈希值,h(“aa”)=h(“ba”)=…=h(“za”)

准备工作:

把每个字符S[i]转换成整数码(ascci,unicode等)
选择一个大素数p

多项式哈希函数

定义
具有固定质数p和所有x, 1<=x<=p-1的哈希函数
这里写图片描述
的集合叫做多项式哈希函数

伪代码实现

hash < ---- 0for i from |S|-1 down to 0    hash <---- (hash * x + S[i])mod preturn hash

定理:对于最大长度为L+1的任意两个不同的字符串s1和s2,如果从Pp中随机选择一个函数h(通过随机选择x),那么产生冲突的最大概率为Pr [h(s1)=h(s2)]= L/p

固定基数m
由于要使用大小为m的哈希表,我们需要基数为m的哈希函数
首先使用Pp中的随机函数h计算字符串的哈希值,然后在使用计算整数哈希值的方法计算所得结果的哈希值

定理:对于最大长度为L+1的任意两个不同的字符串s1和s2,基数为m,那么产生冲突的最大概率为Pr [h(s1)=h(s2)]= L/p+1/m

时间复杂度

对于足够大的p,c=O(1+a)(a为负载因子)
PolyHash(S)的时间复杂度为O(|S|)
如果字符串的长度很短,那么计算h(S)的时间复杂度为O(1)

结论

学习了怎样计算整数和字符串的哈希值
电话簿可以使用两个哈希表实现
电话号码映射到名字,由名字映射到电话号码
搜索和修改的平均时间复杂度为O(1)

0 0
原创粉丝点击