散列表

1.散列查找

涉及到字符串的比较,如果是int类型,可以用查找树、平衡树,效率较高,但字符串比较效率低。考虑将字符串类型进行变换。
查找的方法

  • 顺序查找,O(N)
  • 二分查找, O(log N)
  • 二叉搜索树,O(h),h为二叉查找树的高度
  • 平衡二叉树,O(log N),要求一个字符一个字符的查找

查找问题考虑的方法: 1.有序对象,半序(树)2.直接算出位置:散列

1.1 散列查找
1.1.1散列查找

两个问题:

  • 计算位置:构造散列函数,确定关键词存储位置
  • 解决冲突:应用某种策略解决多个关键词位置相同的问题

时间复杂度:O(1),查找问题与问题规模无关。

装填因子(Loading Factor):设散列表空间大小为m,填入表中元素个数是n,则称下式值为散列表的装填因子
α = n / m \alpha = n/m α=n/m

1)例1:

  • 假设一个数组长11, 数组中的每个数对17求余,得到的值放在一个表中,这个表的key由0-16(有可能有多个值对应表中的同一个位置,需要某种解决此冲突的策略)。
  • 当拿到一个数n时,想要查找表中是否存在此数,就可以用n%17,查看表中对应位置是否存在数据。

2)例2:

  • 对于一个字符串数组,将字符串放入一个二维数组中,维度为[26][2]。将字符串的第一个字符 与’a’的差值做为第一维的index。
  • 假设第一个字符串首位字符为’a’,那么将字符串放入[0][0]的位置;第二个首位为’a’的字符串放[0][1]的位置;当出现第三个首位字符为’a’的字符串时,发生冲突,会溢出。
  • 如果没有溢出,散列表的复杂度为O(1)
1.1.2散列查找的基本思想
  • 以关键字key为自变量,通过一个确定的函数 h h h(散列函数),计算 k e y key key对应的函数值 h ( k e y ) h(key) h(key),作为数据对象的存储地址
  • 可能不同的关键字会映射到同一个散列地址上,即 h ( k e y 1 ) = h ( k e y 2 ) h(key_1)=h(key_2) h(key1)=h(key2),称为“冲突”,需要某种冲突解决策略
1.2散列函数的构造方法

散列函数的要求

  • 方便计算
  • 把不同对象均匀的映射
1.2.1 对于数字关键词
  • 直接定址
    h ( k e y ) = a × k e y + b h(key) = a \times key + b h(key)=a×key+b(例如年份)
  • 除留余数
    h ( k e y ) = k e y % p h(key) = key \% p h(key)=key%p,p一般取表的大小,一般取素数
  • 数字分析法(分析数组的规律,取比较随机的位做散列地址)
    h ( k e y ) = a t o i ( k e y + 7 ) h(key) = atoi(key + 7) h(key)=atoi(key+7),即数组的指针为key,取数组的第7位
    例如身份证号(130 626 1990 1030 2227):
    h ( k e y ) = ( k e y [ 6 ] − ′ 0 ′ ) × 1 0 4 + ( k e y [ 10 ] − ′ 0 ′ ) × 1 0 3 + ( k e y [ 14 ] − ′ 0 ′ ) × 1 0 2 + ( k e y [ 16 ] − ′ 0 ′ ) × 10 + ( k e y [ 17 ] − ′ 0 ′ ) h(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') h(key)=(key[6]0)×104+(key[10]0)×103+(key[14]0)×102+(key[16]0)×10+(key[17]0)
  • 折叠法(把关键词分割成位数相同的几个部分,然后叠加)
  • 平方取中法(把数值取平方,取平方值中间的三位数)
1.2.2 对于字符关键字
  • ASCII码加和法
    h ( k e y ) = ( ∑ k e y ( i ) ) % T a b l e s i z e h(key) = (\sum key(i)) \% Tablesize h(key)=(key(i))%Tablesize把每位字符的ASCii码相加后取余,但会有严重冲突,且结果很容易聚集
  • 前3个字符移位
    h ( k e y ) = ( k e y [ 0 ] × 2 7 2 + k e y [ 1 ] × 27 + k e y [ 2 ] ) % T a b l e s i z e h(key) = (key[0] \times 27^2 + key[1] \times 27 + key[2]) \% Tablesize h(key)=(key[0]×272+key[1]×27+key[2])%Tablesize将结果扩展范围,会有空间浪费Tablesize=26^3
  • 移位法
    h ( k e y ) = ( ∑ i = 0 n − 1 k e y [ n − i − 1 ] × 3 2 i ) % T a b l e s i z e h(key)=(\sum_{i=0}^{n-1}key[n-i-1]\times 32^i) \% Tablesize h(key)=(i=0n1key[ni1]×32i)%Tablesize
    例如"abcde" :1)直接计算((a x 32+b)x 32 + c) x 32 + d ;2)将字符串左移5位 h = (h<<5) + *p++ (p是数组的指针,指针每向后移一次,h向左移5,相当于乘以32)
1.3解决冲突的方法
  • 开放地址法(换个地址,一旦冲突,按照某种规则查找另一个地址 )
    h i ( k e y ) = ( h ( k e y ) + d i ) % T a b l e S i z e h_i(key) = (h(key)+d_i) \% TableSize hi(key)=(h(key)+di)%TableSize,第i次冲突,为地址添加一个偏移量 d i d_i di
    • 线性探测, d i = i d_i = i di=i
    • 平方探测, d i = ( − 1 ) i + 1 i 2 d_i = (-1)^{i+1} i^2 di=(1)i+1i2,超过Tableszie后求余
    • 双散列, d i = i ∗ h 2 ( k e y ) d_i = i*h_2(key) di=ih2(key),设计了两个散列
  • 链地址法(同一位置的冲突对象用链表组织在一起)
1.3.1线性探测法

h i ( k e y ) = ( h ( k e y ) + d i ) % T a b l e S i z e , d i = i h_i(key) = (h(key)+d_i) \% TableSize,\quad d_i = i hi(key)=(h(key)+di)%TableSize,di=i
如果在原计算位置已经有了数据,则发生一次冲突,找到下一个位置,查看是否为空。如果为空,放在这个位置,如果这个位置不为空,则又发生一次冲突,继续查找下个位置。
例:

  • 设数组[47,7,29,11,9,84,54,20,30],数组长度为9,
  • 设计散列表长13,那么装填因子 α = 9 / 13 = 0.69 \alpha=9/13=0.69 α=9/13=0.69
  • 设散列函数为 h ( k e y ) = k e y % 11 h(key)=key \% 11 h(key)=key%11
  • 用线性探测处理冲突,依次插入表

计算 h ( k e y ) h(key) h(key)

key4772911984542030
h(key)3770971098

依次填入散列表对应地址中,若有冲突,用线性探测处理,最终结果为

h(key)0123456789101112
key1130477299845420
冲突次数060010313

有聚集现象,当散列值有冲突为7时,冲突会越来越多

查找性能分析
  • 成功平均查找长度(ASLs):查找每个数据需要比较次数(冲突次数+1)的平均值
    即(1+7+1+1+2+1+4+2+4)/9=2.56
  • 不成功平均查找长度(ASLu):查找不在表中的元素,确定此数值不在表中需要比较的次数。
    • 将不在表中的数值分类为 n % 11 n\%11 n%11的余数为0,1,2,。。。,10的几个类别
    • 例如22,33等余数为0的类别,按照冲突策略查找
      • 先查找位置为0的值,发生冲突(即此位置有值,但不是期待值)
      • 再查找位置为1的值,再次发生冲突,依次往后比较
      • 直到位置为2,值为空,可以确信22,33等数不在表中,此时共比较3次
    • 依次比较余数为1,2,…,12的值,计算比较多少次可以确信此值不在表中。(3+2+1+2+1+1+1+9+8+7+6)/11 = 3.73
1.3.2.平方探测法

h i ( k e y ) = ( h ( k e y ) + d i ) % T a b l e S i z e , d i = ( − 1 ) i + 1 i 2 h_i(key) = (h(key)+d_i) \% TableSize,\quad d_i = (-1)^{i+1} i^2 hi(key)=(h(key)+di)%TableSize,di=(1)i+1i2
例:设表长11

key4772911984542030
h(key)3770971098

依次填入散列表对应地址中,若有冲突,用平方探测处理

h(key)012345678910
key1130204784729954
冲突次数033020100
  • 平方探测最终有可能找不到空位置放入表格,但避免了线性探测的聚集现象。
  • 如果表的大小设计成素数 4 k + 3 4k+3 4k+3,可以证明平方探测一定能找到空位置放入
1.3.3.双散列探测法

h i ( k e y ) = ( h 1 ( k e y ) + d i ) % T a b l e S i z e , d i = i ∗ h 2 ( k e y ) h_i(key) = (h_1(key)+d_i) \% TableSize, \quad d_i = i*h_2(key) hi(key)=(h1(key)+di)%TableSize,di=ih2(key)

  • h 2 ( k e y ) h_2(key) h2(key)是另外一个散列函数,对于任意的key, h 2 ( k e y ) ≠ 0 h_2(key) \neq 0 h2(key)̸=0
  • 探测序列需要保证所有的散列存储单元都能被探测到。可以用 h 2 ( k e y ) = p − ( k e y % p ) , p &lt; T a b l e s i z e h_2(key) = p - (key\% p ),p &lt; Tablesize h2(key)=p(key%p),p<Tablesize,p和tablesize都是素数
1.3.4.再散列(rehashing)
  • 当散列表装填因子太大,查找效率会下降;
  • o . 5 &lt; α &lt; 0.85 o.5&lt;\alpha &lt;0.85 o.5<α<0.85比较实用的最大装填因子。最好小于0.5
  • 再散列:当装填因子过大,需要加倍扩大散列表。此时需要重新计算所有的元素
1.3.5.分离链接法(separate chaining)

同一个位置上有冲突的值用链表串起来。表中存放的是链表的头指针。

1.4散列表的性能分析
  • ASL(平均查找长度)
    • 成功查找
    • 不成功查找

影响产生冲突

  • 散列函数是否均匀
  • 处理冲突的方法
  • 散列表的装填因子 α \alpha α。当装填因子超过0.5,期望次数增长很快
1.4.1.线性探测查找方法

期望探测次数
p = { 1 2 [ 1 + 1 ( 1 − α ) 2 ] , 对插入和不成功查找而言 1 2 ( 1 + 1 1 − α ) , 对成功查找而言 p = \begin{cases} \frac{1}{2}[1+\frac{1}{(1-\alpha)^2}], &amp; \text{对插入和不成功查找而言}\\ \frac{1}{2}(1+ \frac{1}{1-\alpha}),&amp; \text{对成功查找而言}\end{cases} p={21[1+(1α)21],21(1+1α1),对插入和不成功查找而言对成功查找而言

1.4.2.平方探测/双散列查找方法

p = { 1 1 − α , 对插入和不成功查找而言 − 1 α l n ( 1 − α ) , 对成功查找而言 p = \begin{cases} \frac{1}{1-\alpha}, &amp; \text{对插入和不成功查找而言}\\ -\frac{1}{\alpha}ln(1-\alpha),&amp; \text{对成功查找而言}\end{cases} p={1α1,α1ln(1α),对插入和不成功查找而言对成功查找而言

1.4.3.分离链接

这种情况下 α \alpha α有可能超过1,因为链表可以链很多
p = { α + e − α , 对插入和不成功查找而言 1 + α 2 , 对成功查找而言 p = \begin{cases} \alpha + e^{-\alpha}, &amp; \text{对插入和不成功查找而言}\\ 1+\frac{\alpha}{2},&amp; \text{对成功查找而言}\end{cases} p={α+eα,1+2α,对插入和不成功查找而言对成功查找而言

1.5总结
  • 选择合适的 h ( k e y ) h(key) h(key),没有冲突,时间复杂度为O(1)
  • 很多情况下用于字符串管理
  • α \alpha α装填因子为前提,所以散列方法是以空间换时间
  • 散列表的存储对关键字是随机的,不便于顺序查找、范围查找、最大值最小值查找
  • 开放地址法
    • 容易聚集
    • 实现方法为数组,存储效率高
    • 删除时需要“惰性删除”,即标记为"delete"状态,但不真的从空间中删除
  • 分离链接法
    • 顺序存储和链式存储结合,链表部分存储效率和查找效率比较低
    • 关键字不需要 “懒惰删除”,没有存储“垃圾”(因为链表容易实现删除操作)
    • 大的 α \alpha α容易导致空间浪费,但也容易付出更多的查找时间代价
    • 不均匀的链表长度导致效率下降

应用实例:

  • 例1:文件中单词词频统计,并输出词频最大的前10%的单词及词频(设计单词的管理,从词库中查找对应的单词)
  • 例2:统计电话号码出现的次数,将手机号的最后5位做散列函数。用分离连接法解决冲突.
    • 手机号码前三位为网络识别号,不适合做散列函数(容易聚集);又4位是地区编码;最后4位随机性比较好。
    • p(表长度) > 2n(所有数据的个数)
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值