[C++]数据结构:散列表(哈希表)、散列函数构造、处理散列冲突

来源:互联网 发布:刷爱奇艺会员软件 编辑:程序博客网 时间:2024/05/20 11:21

一、散列表(Hash Table, 哈希表)?

维基百科定义:根据关键字(key value)直接访问在内存存储位置的数据结构。

通俗的讲,为了加快查找速度,我们通过同一个映射函数F,建立关键字和记录的存储位置的关系,相应地,记录的连续存储空间称为散列表或哈希表。关键字对应的记录存储位置

称为散列地址

存储位置 = F (关键字)  (函数法则F可自定义)


二、散列函数构造方法

构造原则:a. 计算简单,散列函数计算时间应该尽量少。

        b. 散列地址分布均匀,不仅保证存储空间的有效利用,并减少处理冲突而耗费的时间。

1. 直接定址法

直接取关键字的某个线性函数值为散列地址,即  F( key ) = a * key + b (a, b为常数).适合:需事前知道关键字分布情况,并且查找表较小且连续的情况。此法不常用。

        举个例子,比如,要对0~100岁的人口数字进行统计,我们选用“年龄”这个关键字,直接用年龄的数字作为地址,F(KEY) = KEY.

        地址年龄人数000 500万01 1 600万...................20201500万,,,,,, ............


2. 数字分析法

抽取:使用关键字的一部分来计算散列表存储位置。适合:需事先知道关键字分布状况,且关键字的若干位分布较均匀。此法较常用。

举个例子,手机号是11位,只有最后四位代表真正的用户号,我们可以利用最后四位(如1234),进行反转(4321)、右环移位(4123)等方法进行抽取,并作为地址。

3. 除留余数法
最常用的构造方法。对于散列表长为 m 的散列函数公式为F ( key ) = key mod p ( p < m) , mod 是取模(求余数)。
举个例子,对于一个有12个记录的关键字构造散列表, 用F(KEY) = KEY mod 12 的方法。
 
        关键字{12,25, 38, 15, 16, 29, 78, 67, 56, 21, 22, 47 } , 对应后位置是 {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}。

不过这种方法很容易产生冲突(如果关键字余数大部分相同)。一般地,散列表长为m, 通常p 为小于或等于表长(最好接近m)的最小质数或不包含小于20质因子的合 

数。

4. 其他方法
  还有很多其他构造方法,比如平方取中法,折叠法,随机数法等。我们也可以自己构造哈希函数F,只要能很好的满足构造原则。


三、处理散列冲突的方法

散列冲突:理想情况下,每一个关键字,通过散列函数映射,得到的地址都是唯一的。现实中,常会碰到 key1 != key2 , 但是F (key1)  = F ( key2),把这种现象称为冲突,并把这两个关键字称为这个散列函数的同义词。

散列冲突的出现,将会造成查找错误,我们要想办法解决冲突。


1. 开放定址法

一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到。

计算公式如下:

   F_i (key)=(F(key)+d_i ) mod m (d_i=1,2,3,…,m-1)

举个例子,关键字集合{12,67,56,16,25,37,22,29,15,47,48,34} ,表长12。 f (key) = key mod 12.

计算前五个数{12,67,56,16,25},无冲突,直接存入,得表1:

计算key = 37, f (37) = 1, 与 25所在位置冲突,应用 f (37) = ( f (37) +1 ) mod 12 = 2 , 将37存在下标为 2 的位置,得到表2: 

接下来存{22,29,15,47} 都没有冲突,正常存入,得表3:


存key = 48, f (48) = 0, 与12冲突, 计算f (48) = ( f (48) +1 ) mod 12 = 1,与25冲突,计算f (48) = ( f (48) +2 ) mod 12 = 2,依旧冲突,继续做下去,知道 f (48) = ( f (48) +6 ) mod 12 = 6, 存入表中。


这样看,我们用每次增加一个单位来寻找空位,有时候很麻烦,所以我们采用d_i = 1^2, (-1)^2, 2^2, (-2)^2....q^2, (-q)^2的方法进行双向查找。也称为二次探测法。也可以取d_i是随机数,称为随机探测法,不过要保证采用同样的随机种子。才能既可以存,也可以准 

确找到。


2. 链地址法

能不能产生冲突时,不移位?答案是可以。只要将关键字为同义词的记录存储在一个单链表中,把这种表称为同义词子表,在散列表中只存储所有同义词子表的头指针。缺点是会增加查找时遍历单链表的性能损耗。结构如图:


3. 公共溢出区法

所有冲突的关键字都存储到另外一张表中(溢出表),如图:(查找时现在基本表查,没有找到再去溢出表,比较适合冲突较少的情况)

还有一些其他的解决冲突的方法,比如构造多个散列函数。










0 0
原创粉丝点击