散列表查找(哈希表)知识点

来源:互联网 发布:恒腾网络 股价 编辑:程序博客网 时间:2024/05/22 13:21

本文目录

  1. 定义
  2. 散列表查找步骤
  3. 使用散列表存在的问题
  4. 散列函数的构造方法
  5. 处理冲突的方法

1. 定义

相比于比较查找法,散列表的查找是通过计算来进行实现的,我们只需要通过某个函数f,使得存储位置=f(关键字),使得每个关键字key对应一个存储位置f(key)。这里我们把这种对应关系f称为散列函数,又称为哈希(Hash)函数。故采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash Table)。关键字对应的记录存储位置我们称为散列地址。

2. 散列表查找步骤

  1. 第一步,存储时通过散列函数计算记录的散列地址,并按此散列地址存储该记录。
  2. 第二步,查找时通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录。
    因此,散列技术既是一种存储技术,也是一种查找方法。

3. 使用散列表存在的问题

  • 散列技术最适合的求解问题是查找与给定值一一对应的记录
  • 散列表也不适合范围查找
  • 冲突(collision):两个关键字key1不等于key2,但是却有f(key1)=f(key2),并且key1和key2称为这个散列函数的同义词(synonym)。

3. 散列函数的构造方法

构造原则

  1. 计算简单:散列函数的计算时间不应该超过其他查找技术与关键字比较的时间。
  2. 散列地址分布均匀:尽量让散列地址均匀分布在存储空间中。

经典构造方法

直接定址法
取关键字的某个线性函数值为散列地址,即: f(key)=a*key+b(a、b为常数)
比如:统计0-100岁的人口数字,可以直接使用年龄的数字作为地址。

数字分析法
采用抽取的方法,使用关键字的一部分来计算散列存储位置的方法。通常适用于处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑。

平方取中法
假设关键字是1234,那么他的平方就是1522756,再抽取中间的三位就是227,作为散列地址。平方取中法比较适合不知道关键字的分布,而位数又不是很大的情况。

折叠法
折叠法是将关键字从左到右分割成位数相等的几部分(注意最后一部分位数不够可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。比如我们的关键字是9876543210,散列表表长为三位,我们将它分为四组,987|654|321|0,然后将他们叠加求和得1962,再求后3位得到散列地址为962,也可以从一端向另一端来回折叠后对齐相加。比如我们将987和321反转,再与654和0相加,变成1566,取566作为散列地址。
折叠法适合事先不需要知道关键字的分布,但是位数较多的情况。

除留余数法
此方法为最常用的构造散列函数方法。对于散列表长为m的散列函数公式为:
f(key)=key mod p (p<=m),关键在于选择合适的p。
根据经验,若散列表长为m,通常p为小于或等于表长(最好接近于m)的最小质数或不包含小于20质因子的合数。

随机数法
选择一个随机数,取关键字的随机函数值为它的散列地址。也就是f(key)=random(key)。这里random函数是随机函数,当关键字的长度不等时,采用这个方法构造散列函数是比较合适的。

如果关键字是字符串,可以转化为ASCII码或者Unicode码来处理

处理冲突的方法

开放定址法
开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。

fi(key) = (f(key)+di) MOD m (di=1,2,3,……,m-1)

用开放定址法解决冲突的做法是:当冲突发生时,使用某种探测技术在散列表中形成一个探测序列。沿此序列逐个单元地查找,直到找到给定的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探测到开放的地址则表明表中无待查的关键字,即查找失败。

比如说,我们的关键字集合为{12,67,56,16,25,37,22,29,15,47,48,34},表长为12。 我们用散列函数f(key) = key mod 12,当计算前S个数{12,67,56,16,25}时,都是没有冲突的散列地址,直接存入:
这里写图片描述
计算key = 37时,发现f(37) = 1,此时就与25所在的位置冲突。
于是我们应用上面的公式f(37) = (f(37)+1) mod 12 = 2。于是将37存入下标为2的位置。这其实就是房子被人买了于是买下一间的作法:这里写图片描述
到了 key=48,我们计算得到f(48) = 0,与12所在的0位置冲突了,不要紧,我们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时,才有空位,机不可失,赶快存入:
这里写图片描述
我们把这种解决冲突的开放定址法称为线性探测法
从这个例子我们也看到,我们在解决冲突的时候,还会碰到如48和37这种本来都不是同义词却需要争夺一个地址的情况,我们称这种现象为堆积。很显然,堆积的出现,使得我们需要不断处理冲突,无论是存入还是査找效率都会大大降低。

二次探测法

fi(key) = (f(key)+di) MOD m (di = 1平方, -1平方, 2平方, -2平凡发,……, q2, -q2, q <= m/2)

我们可以改进di = 12, -12, 22, -22,……, q2, -q2 (q <= m/2),这样就等于是可以双向寻找到可能的空位置。我们称这种方法为二次探测法。

随机探测法
还有一种方法是,在冲突时,对于位移量 di 采用随机函数计算得到,我们称之为随机探测法。

fi(key) = (f(key)+di) MOD m (di是一个随机数列)

再散列函数法

fi(key)=RHi(key) (i=1,2,…,k)
每当发生散列地址冲突时,就换一个散列函数计算,这种方法能够使得关键字不产生聚集,相应的也增加了计算的时间。

链地址法
将所有关键字为同义词的记录存储在一个单链表中,我们称这种表为同义词子表,在散列表中只存储所有同义词子表的头指针。
对于关键字集合{12,67,56,16,25,37, 22,29,15,47,48,34},我们用前面同样的12为除数,进行除留余数法:
这里写图片描述

公共溢出区法
冲突的关键字放在另外一个单链表中,当无法在散列表中查询时,就在溢出表进行顺序查找。

原创粉丝点击