散列表之散列函数

30 篇文章 1 订阅
30 篇文章 6 订阅

散列表之散列函数

我们在之前的文章《散列表之链接法》中已经提到过,散列函数是散列表的一个难点,一个好的散列可以很大程度上提升散列表的查找和删除操作的速度,而一个设计差劲的散列表的,查找和删除操作的运行时间将和链式链表一样,将达到 O(n) 。所以设计一个表现良好的散列函数尤为重要!

什么是好的散列函数

一个好的散列函数应该满足简单均匀散列的假设:

每一个关键字都被等可能的散列到m个槽中的任何一个,并与其它关键字散列到那个槽无关。

在实际应用中,我们可以通过利用关键字有用的分布信息来设计一个表现良好的散列函数。假设现在我们需要散列一些英文字符串,其中有一些比较相近的单词,比如the,then,he等等,好的散列函数是将这些键值比较相近的字符串映射到相同槽的可能性最小化。

将关键字转化为自然数

什么?关键字除了自然数难道还有其他么?是的,关键字除了自然数还有许多其他的种类,比如字符串,浮点数,一个类对象,一些可以将自身转化为自然数的类型。转化为自然数以后,进行一些计算映射工作就得到槽的位置了。

Alt text

比如对于一个字符串来说,我们将每个字符的ASCII码相加就得到一个自然数。一个小数的话,可以进行小数的截断或者通过乘 10n 来将小数扩大为自然数,而对于一个类对象来说,可以自定义一个将类对象转为自然数的函数方法。

举个例子:

class Person
{
    //menbers
    int age;
    string name;
    //function 转为自然数
    size_t toN() const
    {
        /** 将age和name进行一些计算以后返回一个自然数 */
    }
}

散列函数的三种设计方法

接下来我们将介绍三种散列函数的设计方法,他们分别是:

  • 除法散列法
  • 乘法散列法
  • 全域散列法

除法散列法

除法散列法的定义,假设有一元素 E ,其键值为k,将键值 k 映射到m个槽上的某一个,即散列函数为:

h(k)=k modm

比如 m=3 , k=10 ,那么 h(k)=1

除法散列法注意事项:
在应用触发散列法时,要避免 m 的某些值,例如m不应该为2的幂,因为如果 m=2p ,则 h(k) 就是 k 的最低p位数字(因为我们除以 2p 就是将 k 右移p位,那么取余就是 k 的最低p位数字),除非已经知道各种最低的 p 位的排列形式是等可能的,否则在设计散列函数的时候,最好考虑关键字的所有位

乘法散列法

构造散列函数的乘法散列法包含了两个步骤。第一步,用关键字k乘上常数 A(0<A<1) ,并提取 kA 的小数部分。第二步,用 m 乘以这个值,并向下取整:

h(k)=m(kAmod1)

全域散列法

假设现在你写了一款软件,打算卖给一家公司,可是另外一个人也写了一款可以实现和你相同功能的软件,也打算卖给这家公司。于是公司就叫你们在看的见对方全部源码的情况下为对方写测试用例,最后那个软件运行速度快就买那个。假设双方在代码里面都使用了散列表,并且都设计了对应的散列函数,那么你的竞争对手就会针对你的散列函数写一个将全部键值都映射到同一个槽的恶意测试用例,而你也会这样写一组针对对方散列函数的恶意测试用例。那你要怎么才能在这场竞争中胜出呢?

没错,那就是随机,你写了若干个性能优良的散列函数,然后再运行的时候随机在里面选出一个散列函数进行映射,这样你的竞争对手就无法写出针对你的散列函数的恶意测试用例了

在前面我们讲过的除法散列法乘法散列法都是一个固定的散列函数,在全域散列法里面,散列函数都是随机的,不过这些随机的散列函数可不是任意设计的。

H 是一组有限散列函数集合,它将给定的关键字全域U映射到 {0,1,2,3,...,m1} ,这样的一个函数称为全域的,如果对于每一对不同的关键字 k,lU ,满足 h(k)=h(l) 的散列函数 hH 的个数至多是 |H|/m ,也就是说,从 H 中选取一个散列函数h,在 kl 的情况下, h(k)=h(l) 的概率不大于 1/m 。这也正好是从集合 0,1,2,3...,m1 中独立的随机选取 h(k) h(l) 发生冲突的概率。

那要怎么设计一个全域散列函数类呢?

  1. 首先选取一个足够大的素数 p ,使得每一个可能的关键字落到0 p1 的范围内。设 Zp={0,1,2,...,p1} , Zp={1,2,3,...,p1}
  2. 现在对于 aZp , bZp ,定义散列函数 hab .利用一次线性变换,进行模 m 和模p的归约,有
    hab(k)=((ak+b)modp))modm

    于是构成了这样的散列函数簇:
    Hpm={hab:aZp,bZp}

    定理: Hpm 是全域的
    证明:考虑 Zp 中两个不同的关键字 k l,即 kl ,对于某一个给定的散列函数 hab ,设:
    r=(ak+b)modps=(al+b)modp

    上面两式相减:
    rsa(kl)modp

    因为 p 是素数,且a modp0 (kl) modp0 的,所以 a(kl)modp0 ,即 rs
    r s为随机选取不同的值时,不同的关键字 k l发生冲突的概率为 rs(modp) 的概率,对于某个给定的 r 值,s的可能取值就是余下的 p1 种,其中满足 sr sr(modm) s 值的数目至多是:
    p/m1((p+m1)/m)1=(p1)/m

    所以有:
    Prhab(k)=hab(l)1/m
  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值