哈希表/散列表原理解析

什么是哈希表?

哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
记录的存储位置=f(关键字)

这里的对应关系f称为散列函数,又称为哈希(Hash函数),采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。

哈希表中的四个概念?
关键字(Key)
(哈希表是通过一个信息来查找另一个信息,将这两个信息在哈希表从形成映射关系,而关键字则是我们要提供的信息)
值(Value)
(值是我们想要获取到的信息)
哈希函数
(哈希函数是用来构成哈希表的工具,也是哈希表的核心思想,是关键字和对应数据的存储位置的一个映射关系,通过把关键字代入哈希函数中进行计算,可以得到关键字所对应的数据在哈希表中的存储位置,)
哈希地址
(哈希地址记录的是我们所需要的数据在哈希表中的存储位置,哈希地址只是表示查找表中的存储位置,不是实际的物理存储位置)
关键字、值、哈希函数、哈希地址、哈希表之间的关系
哈希表是通过哈希函数来构建的,我们把哈希函数想象成数学中的函数f()。而函数中的X就是关键字Key,既f(x)。然后将关键字带入一个公式中,如f(x) = x * 2 + 3 。然后经过运算就可以求出一个值,这个值表示要查询的数据(Value)在哈希表中的存储位置,也就是哈希地址。而记录这整个Key-Value信息的表就是哈希表。

我们现在把一个抽象的哈希表具体表现成下面的形式,可以说下面就是一个哈希表。我们把它想象成一本电话簿,该电话簿用拼音首字母来区分数据,数据记录了具体的人名和其电话号码。换成哈希表的概念,就是说ABCD是哈希地址,用于记录值所存储的位置。人名为查询关键字(Key)。电话号码是我们需要获得的数据(Value)。

哈希地址 Key and Value
A 艾力 13912345678
B 包三 15823457890
C 成五 15823457890
… …
F 付六 15823457890
G 高飞 15823457890
… …
为了简化流程且易懂,我们这里假设了哈希地址就是关键字的拼音首字母大写,那么哈希函数就是f(艾力)= A,f(包三) = B等…。我们通过将关键字(包三)代入计算公式(哈希函数)中,得到包三的电话号码所在的位置,既哈希地址(B)。那我们就能通过位置直接获得我们想要的数据,而不需要遍历比较。
什么是哈希冲突
哈希冲突就是key1!=key2.但key1和key2所对应的数据的存储位置都一致,既哈希地址一致。这就是哈希冲突。我们看来下面的哈希表:

哈希地址 Key and Value
A 艾力 13912345678 ;爱丽丝 15823787890
B 包三 15823457890
如上表我们可以看到,艾力和爱丽丝两个人的拼音首字母大写都是A,这就意味着这两个人的电话号码都存储在哈希表(电话簿)的A区,这就存在冲突了。可能有人会说在这个例子中,这在现实意义上,好像也没什么问题,因为很多姓的拼音首字母都可以是A,一个A区的范围很大。但是这仅仅是我们哈希函数设计的太简单,如果我们哈希函数这么设计,我们取关键字拼音首字母对应的ASCII表的值与关键字拼音尾字母在ASCII表的值相乘再除以15取整得到的值作为哈希地址,这似乎就可以在一定程度上减少了哈希冲突的概率。(但实际上这个算法也太简单了)

特性:
通常情况下,可用关键字的集合大于哈希地址集。假设表常为m,则地址最大则是0到n-1。但关键字则可以有很多种可能,比如关键字可以定义为字母为首的8位字母或数字,那么关键字的集合大小为1.2888899*E+14。而表长仅仅1000。地址集合也就0到999。所以这就意味着哈希函数是一个压缩映像,这里的哈希冲突根本无法避免。那我们可以做的只是尽量避免冲突,所以我们可以将冲突的水平平均化,把关键字映射到地址集合中的每个一地址的概率是相等的,也就是我们后面会说到的均匀的哈希函数。

总结
所以在构建哈希表中,最为重要的核心就是哈希函数的设计。哈希冲突是一种想象,只能尽可能减少,是不能完全避免的
怎么样才是好的哈希函数
均匀的哈希函数:
若对于关键字集合中的任一个关键字,经过哈希函数映射到地址集合的任何一个地址的概率都是相等的,则称此类哈希函数为均匀的(Uniform)哈希函数,从而减少哈希冲突。

如何判断一个哈希函数的优劣:

能否将关键字均匀影射到哈希空间上
有无好的解决冲突的方法,
计算哈希函数是否简单高效。
构建哈希的算法
1.直接定值法
直接定制法的哈希函数是一个一次函数,取关键字和关键字的某个线性函数值为哈希地址,有如下两种形式:

第一种 第二种
f ( Key ) = key f ( key ) = a * key + b
第一种形式的哈希地址就是Key关键字本身,第二种形式其中a和b是常数,通过关键字与常数的计算获得关键字。这类哈希函数叫做自身函数

举个例子:

直接定制法的哈希表非常简单。如图上,我们想要查询26岁的人有多少个,我们直接传入关键字26。哈希函数直接就返回哈希地址26,我们直接取哈希表的第26个位置的值既可。以上是第一种形式的范例,第二种也差不多,这里就不举例了。相对来说非常简单。不过在实际的使用中,这类哈希函数的使用情况较少。
2.数字分析法
如果关键字由多位字符或者数字组成,就可以考虑抽取其中的 2 位或者多位作为该关键字对应的哈希地址,在取法上尽量选择变化较多的位,避免冲突发生。

举个例子:
在这里插入图片描述

上图展示出了大概8个关键字,每个关键字都是8位数的十进制数字。经过分析,我们会发现几个特征:

第1位和第2位的值都是固定不变。
第3位不是3就是4
第8位只在2、5、7中徘徊
综上所述,只有中间4位数的值接近随机。所以为了避免冲突,我们从4位接近随机的位数中取任意两位或者取其中两位与另外两位的和,再做舍去进位处理后得到的结果作为哈希地址。
3.平方取中法
平方取中法是对关键字做平方操作,取中间几位作为哈希地址(此方法是比较常用的构造哈希函数的方法)
例如关键字序列为{421,423,436},对各个关键字进行平方后的结果为{177241,178929,190096},我们则可以取中间的两位{72,89,00}作为其哈希地址。
4.除留余数法
若已知整个哈希表的最大长度 m,可以取一个不大于 m 的数 p,然后对该关键字 key 做取余运算,即

f(key) = key % p
除留余数法也是最常用,也最简单的哈希函数构造方法。它不仅仅可以直接去模,也可以在折叠,平方取中后再去模。

注意:
除留余数法对p有很高的要求。若p选取的不好,很容易产生同义词。由以往经验可得,p一般取质数或不包含小于20的质因数的合数
5.随机数法
随机数法既是取关键字的一个随机函数值作为它的哈希地址,适用于关键字长度不一的情况,如

f (key) = random (key)

注意:
这里的随机函数其实是伪随机函数,真随机函数是即使每次给定的 key 相同,但是 H(key)都是不同;而伪随机函数正好相反,每个 key 都对应的是固定的 H(key)。
6.折叠法
是将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。关键字位数很多,且关键字每一位上数字分布大致均匀时,可以采用折叠法。折叠又可以分为两种:

移位折叠
(移动折叠是将分割后的每一部分的最低位对齐,然后相加)
间界折叠
(间接叠加是从一端向另一端来回折叠,然后相加)
举个例子:
现有图书馆中某藏书的编号为0-442-20586-4,先对其分别采用移位折叠(a)和间界折叠(b)。如下图
在这里插入图片描述

(a) 移位折叠将图书编号作为关键字,分割为几个小的部分,然后以最低位对齐,然后相加,再舍去进位,得4位数作为哈希地址

(b) 间界折叠就类似于折纸的步骤。从一端向另一端折叠。同样是分割几个小的部分,然后最低位对齐,相加,舍去进位。与移位折叠不同的是,分割部分的数字序列顺序不同。

猜你喜欢

转载自blog.csdn.net/qq_42259940/article/details/88015461