为什么要构建哈希表?

为什么要构建哈希表?

现在有一组数据,我们想查找一个值(x)是否在这组数据中,通常来说,我们需要把这组数据遍历一遍,来看看有没有x这个值。

这时,我们发现这样查找数据要花费的时间复杂度为O(n),链表、顺序表都是如此。

为了降低查找数据的时间复杂度,那我们就不能去遍历所有的数据来查找,我们需要找到新的方法来查找数据,这时我们就引入了哈希表。

哈希

哈希就是根据设定好的哈希函数和处理发生哈希冲突的解决方法,哈希是一种存储方法,也是一种查找方法,算法中只要用到这种哈希思想,就可以叫做哈希算法(hash)

哈希函数

1.定义

将一组数据的关键字映射到连续且有限的地址集(区间)可以将映射到地址集上的像记录在表中,这个表就是我们说的哈希表,这个映射的过程一般叫做哈希造表 或者叫散列,这个映射关系叫做哈希函数或者散列

2.构造方法
1)直接定址法

取其关键字或者关键字的某个线性函数值为哈希地址。即:
f ( k e y ) = a × k e y + b ( a 、 b  为常数  ) f(k e y)=a \times k e y+b \quad(a 、 b \text { 为常数 }) f(key)=a×key+b(ab 为常数 )
例如:现在有一个0~100岁的人口统计表,其中年龄作为关键字哈希函数取关键字:
f ( k e y ) = k e y f(k e y)= k e y f(key)=key
直接定址
如果我们要统计1980年后出现的人口数,那么我们就可以对出生年份这个关键字减去1980来作为地址,即:
f ( k e y ) = k e y − 1980 f(k e y)= k e y-1980 f(key)=key1980
在这里插入图片描述

2)数字分析法

假设关键字是以r为基的数(如:以10为基的十进制数),并且哈希表可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。
例如:我们的11位手机号“130xxxx1234”,前三位表示不同运营商公司的子品牌,中间四位是HLR识别号,表示用户的归属地,后面四位才是真正的用户号,如下图所示

手机号

那么如果我们要存储某家公司员工登记表,如果用手机号作为关键字,那么很有可能前7位是相同的,那么选取后四位作为哈希地址就是不错的选择。

3)平方取中法

这个方法计算比较简单,假设关键字是1234,那么它的平方就是1522756,再抽取中间的3位就是227,用做哈希地址。这种方法比较适合不知道关键字的分布,而位数又不是很大的情况。

4)折叠法

折叠法是将关键字从左到右分割成位数相等的几部分,然后将这几部分叠加求和,并按照哈希表表长,取后几位作为哈希地址。
比如我们的关键字是9876543210,哈希表表长为3位,我们将其分为四组987|654|321|0,然后将它们叠加求和987+654+321+0=1962,在求后三位得到哈希地址为962.
折叠法事先不需要知道关键字的分布,适合关键字位数比较多的情况。

5)除留余数法

此方法是最常用的构造哈希函数的方法。对于哈希表长为m的哈希函数公式为:
f (  key  ) =  key    m o d   p ( p ⩽ m ) f(\text { key })=\text { key } \bmod p(p \leqslant m) f( key )= key modp(pm)
mod是取模(求余数)的意思,并且此方法不仅可以直接对关键字取模,也可以在折叠、平方取中后再取模。此方法的关键在于选取合适的p,如果选的不好很容易造成哈希冲突。
如对下表,我们对于有12个记录的关键字构造哈希表时,就用了 f (  key  ) =  key  .   m o d   12 f(\text { key })=\text { key } .\bmod 12 f( key )= key .mod12的方法,比如 29 m o d 12 = 5 29mod12=5 29mod12=5,所以它存储在下表为5的位置。

6)随机数法

选取一个随机数,取关键字的随机函数值作为它的哈希地址,也就是 f ( k e y ) = r a n d o m ( k e y ) f(key)=random(key) f(key)=random(key).这里 r a n d o m random random是随机函数。当关键字的长度不等时,采用这个方法比较合适。

哈希冲突

1.定义

不同数据的关键字通过哈希函数得到一个相同的地址,这时就发生冲突,这种冲突一般叫做哈希冲突(存放位置有值)。

2.构造方法
1)开放定址法

此方法就是一旦发生了冲突,就去寻找下一个空的哈希地址,只要哈希列表足够大,空的哈希地址总能找到,并将记录存入。
公式为: f i (  key  ) = ( f (  key  ) + d i ) MOD ⁡ m ( d i = 1 , 2 , 3 , ⋯ ⋯   , m − 1 ) f_{i}(\text { key })=\left(f(\text { key })+d_{i}\right) \quad \operatorname{MOD} m\left(d_{i}=1,2,3, \cdots \cdots, m-1\right) fi( key )=(f( key )+di)MODm(di=1,2,3,,m1)

2)再哈希函数法

对于我们的哈希函数来说,我们可以事先准备多个哈希函数
f i (  key  ) = R H i (  key  ) ( i = 1 , 2 , ⋯   , k ) f_{i}(\text { key })=R H_{i}(\text { key }) \quad(i=1,2, \cdots, k) fi( key )=RHi( key )(i=1,2,,k)
这里 R H i R H_{i} RHi就是不同的哈希函数,可以把我们之前说的除留余数、折叠、平方取中等方法全部用上。每当哈希地址冲突时,就换一个哈希函数计算,相信总会有一个可以把冲突解决掉,当然这种方法会相应的增加计算的时间。

3)链地址法

我们将所有关键字为同义词的记录存储在一个单链表中,称这种表为同义词子表,在散列表中只存储所有同义词子表的头指针。对于关键字集合{12,67,56,16,25,37,22,29,15,47,48,34},我们用前面同样的12为除数,进行除留余数法,可得到如下图结构,此时,已经不存在冲突换址的问题,无论有多少个冲突,都只是在当前位置给单链表增加结点的问题。
在这里插入图片描述
链地址法对于可能会造成很多冲突的哈希函数来说,提供了绝不会出现找不到地址的保障。当然,这也就带来了查找时需要遍历单链表的性能损耗。

4)公共区溢出法

这个方法就是建立一个溢出表,把所有冲突的数据都存进去,我们给所有冲突的关键字建立了一个公共溢出区来存放。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值