散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。
根据key值来直接访问数据,查找速度快
一、哈希表和字典的区别
- 概念与实现方式:
- 哈希表:基于哈希函数实现的数据结构,它将数据映射到一个固定位置的数组中。通过哈希函数将关键字映射为一个索引值,然后在该索引位置上进行数据的插入、查找和删除操作。
- 字典:在Python等编程语言中,字典是内置的、使用哈希表实现的数据结构。它提供了存储键值对的功能,每个值与唯一的键相关联,可以方便地通过键来访问对应的值。
- 编程语言:
- 哈希表:是一种通用的数据结构,可以在多种编程语言中实现。
- 字典:是特定编程语言(如Python)中的特定实现,提供了额外的功能和语法糖,如迭代、切片等。
- 顺序性:
- 哈希表:键值对通常是无序存储的,即插入的顺序不一定与遍历的顺序相同。
- 字典:在Python中,字典维护了插入键值对的顺序,在遍历时会按照插入的顺序返回键值对。
- 线程安全性:
- 哈希表:在某些实现中,哈希表是线程安全的,适合在多线程环境中使用。
- 字典:在Python中,字典是线程不安全的,因此在多线程环境中需要额外的同步机制。
- 数据类型处理:
- 哈希表:不是泛型的,处理数据类型时可能需要经过装箱、拆箱操作,效率相对较低。
- 字典:是泛型的,数据不需要经过装箱、拆箱操作,效率更高。
- 其他特点:
- 哈希表:最大的优势在于其索引方式,经过散列处理,在数据量大的时候查找速度优势明显。
- 字典:具有动态伸缩性,可以自动根据需要进行扩容,以适应不同数量的键值对。此外,字典可以存储不同类型的值,非常灵活。
二、哈希函数
哈希函数(Hash Function):将哈希表中元素的关键键值映射为元素存储位置的函数。
关于整数类型的关键字,通常用到的哈希函数方法有:直接定址法、除留余数法、平方取中法、基数转换法、数字分析法、折叠法、随机数法、乘积法、点积法等。下面我们介绍几个常用的哈希函数方法。
-
直接定址法:
- 定义:哈希函数为关键字的线性函数,即
H(key) = key
或H(key) = a * key + b
,其中a和b为常数。 - 特点:地址集合的大小等于关键字集合的大小。
- 应用:适用于关键字分布基本连续的情况,如从1到100岁的人口数字统计表。
- 定义:哈希函数为关键字的线性函数,即
-
除留余数法:
- 定义:取关键字被某个不大于哈希表表长m的数p除后所得的余数为哈希地址,即
H(key) = key mod p
,其中p <= m。 - 特点:简单,易于实现,是实际应用中最常用的方法之一。
- 应用:广泛应用于各种哈希表的构建中,特别是在处理冲突时。
- 定义:取关键字被某个不大于哈希表表长m的数p除后所得的余数为哈希地址,即
-
平方取中法:
- 定义:对关键字进行平方操作,然后取中间的几位数作为哈希地址。
- 特点:适用于关键字分布较为均匀的情况。
- 应用:在关键字长度较短且分布较为均匀时,可以有效避免哈希冲突。
-
基数转换法:
- 定义:将关键字看作另一种进制的数,再转换成原来进制的数,然后选其中几位作为哈希地址。
- 特点:通过改变进制来改变关键字的表示形式,有助于减少哈希冲突。
- 应用:在处理关键字较长且分布不均匀时,可以通过基数转换法来优化哈希函数的性能。
-
数字分析法:
- 定义:如果关键字由多位字符或数字组成,可以抽取其中的某几位作为哈希地址。
- 特点:需要根据关键字的具体分布来选择抽取的位数,以尽量减少哈希冲突。
- 应用:在关键字具有某种规律或模式时,数字分析法可以作为一种有效的哈希函数方法。
- 例子:假设我们有一组身份证号码作为关键字,并且注意到身份证号码中的某几位(例如最后四位)在数据集中较为分散。我们可以选择这四位作为哈希地址。
- 假设身份证号码为123456789012345678,我们选择最后四位5678作为哈希地址。
-
折叠法:
- 定义:将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。
- 特点:适用于关键字位数较长且没有明显规律的情况。
- 应用:在处理长字符串或长数字作为关键字时,折叠法可以作为一种有效的哈希函数方法。
- 例子:考虑一个较长的整数关键字1234567890,我们可以将其分为两部分12345和67890,然后相加(忽略进位)得到79235作为哈希地址。
-
随机数法:
- 定义:取关键字的一个随机函数值作为哈希地址,即
H(key) = random(key)
。注意这里的随机函数实际上是伪随机函数。 - 特点:每个关键字都对应一个固定的哈希地址,但地址的生成是随机的。
- 应用:在关键字长度不等或关键字分布不均匀时,随机数法可以作为一种简单的哈希函数方法。
- 例子:由于随机数法通常涉及伪随机数生成器,假设有一个伪随机数生成器能够根据关键字生成哈希地址。对于关键字“egg”,伪随机数生成器可能返回123作为哈希地址
- 定义:取关键字的一个随机函数值作为哈希地址,即
三、哈希冲突
哈希冲突(Hash Collision)是一种在哈希表(Hash Table)或哈希函数中可能出现的现象,它指的是两个不同的输入值(通常称为键或关键字)经过哈希函数计算后得到了相同的哈希值(哈希地址或哈希索引)。这种现象可能会导致数据检索和存储的问题,因为哈希表通常使用哈希值来确定数据在表中的位置。
简而言之就是一山有两只老虎,得避免这种情况
哈希冲突的原因主要有以下几点:
- 哈希函数设计不合理:如果哈希函数的设计不合理,可能会导致输入值在哈希函数计算后的分布不均匀,进而增加哈希冲突的概率。例如,某些哈希函数可能对于特定的输入模式或数据集特别敏感,导致大量不同的输入值产生相同的哈希值。
- 哈希表容量过小:当哈希表的容量较小,而要存储的数据较多时,就容易出现哈希冲突。因为哈希表的大小限制了可用的哈希地址的数量,当数据项的数量接近或超过哈希表的大小时,冲突的概率就会显著增加。
- 数据集特征:当数据集的特征与哈希函数的设计不匹配时,也可能导致哈希冲突的增加。例如,如果数据集中的键具有某种特定的模式或分布,而哈希函数没有考虑到这种模式或分布,那么就可能导致哈希冲突的增加。
为了解决哈希冲突,可以采用以下几种方法:
- 开放定址法:当发生哈希冲突时,通过探测哈希表中的下一个未被占用的位置,直到找到一个空槽来存储数据。具体实现包括线性探测法、平方探测法(二次探测)等。
- 线性探测法:当所需存放值的位置被占时,往后一直加1并对哈希表长度取模,直到找到一个空余的地址。
- 平方探测法:当所需存放值的位置被占时,会前后寻找而不是单独方向的寻找,通常使用平方数作为探测的步长。
- 再哈希法:同时构造多个不同的哈希函数,当发生哈希冲突时,就使用第二个、第三个等其他的哈希函数计算地址,直到不发生冲突为止。
- 链地址法:将所有哈希地址相同的记录都链接在同一链表中。这样,即使多个键具有相同的哈希值,它们也可以被存储在同一个链表中,而不会导致冲突。
- 建立公共溢出区:将哈希表分为基本表和溢出表,将发生冲突的都存放在溢出表中。这种方法将冲突的数据项存储在单独的表中,从而避免了在基本表中处理冲突的开销。
四、总结
哈希表是一种高效的数据结构,它利用哈希函数将键映射到桶或槽中,以便快速访问、插入和删除数据。在实际应用中,我们需要根据具体的需求选择合适的哈希函数、处理哈希冲突的方法以及优化哈希表的性能。通过合理地设计和使用哈希表,我们可以显著提高程序的运行效率。