数据结构中的哈希表:Python 示例
什么是哈希?
哈希值是一个固定长度的值,它使用数学公式生成。哈希值用于数据压缩、密码学等。在数据索引中,哈希值被使用,因为无论用于生成它们的值是什么,它们都具有固定的长度。这使得哈希值与可变长度的其他值相比,占用空间更小。
哈希函数使用数学算法将键转换为哈希。当哈希函数为多个键产生相同的哈希值时,就会发生冲突。
什么是哈希表?
哈希表是一种数据结构,它使用键值对存储值。每个值都分配了一个使用哈希函数生成的唯一键。
键的名称用于访问其关联的值。这使得在哈希表中搜索值非常快,无论哈希表中有多少项。
哈希函数
例如,如果我们想存储员工记录,并且每个员工都使用员工编号唯一标识。
我们可以使用员工编号作为键,并将员工数据作为值。
上述方法将需要额外的免费空间,其数量级为 (m * n2),其中变量 m 是 数组的大小,变量 n 是员工编号的位数。这种方法引入了存储空间问题。
哈希函数通过获取员工编号并使用它来生成哈希整数值、固定位数,并优化存储空间来解决上述问题。哈希函数的目的是创建一个键,用于引用我们要存储的值。该函数接受要保存的值,然后使用算法计算键的值。
下面是一个简单的哈希函数的示例
h(k) = k1 % m
此处,
- h(k) 是接受参数 k 的哈希函数。参数 k 是我们要计算键的值。
- k1 % m 是我们哈希函数的算法,其中 k1 是我们要存储的值,m 是列表的大小。我们使用模运算符来计算键。
示例
假设我们有一个固定大小为 3 的列表以及以下值
[1,2,3]
我们可以使用上述公式来计算每个值应占用的位置。
下图显示了我们哈希表中可用的索引。
步骤 1) 如下计算第一个值将占用的位置
h(1) = 1 % 3
= 1
值 1 将占用 索引 1 上的空间
步骤 2) 计算第二个值将占用的位置
h(2) = 2 % 3
= 2
值 2 将占用 索引 2 上的空间
步骤 3) 计算第三个值将占用的位置。
h(3) = 3 % 3
= 0
值 3 将占用 索引 0 上的空间
最终结果
我们填充的哈希表现在如下所示。
好的哈希函数的特性
一个好的哈希函数应具备以下特性。
- 生成哈希的公式应在算法中使用要存储的数据的值。
- 即使输入数据量相同,哈希函数也应生成唯一的哈希值。
- 该函数应尽量减少冲突次数。当为多个值生成相同值时,会发生冲突。
- 值必须在所有可能的哈希中一致地分布。
冲突
当算法为多个值生成相同的哈希时,就会发生冲突。
让我们看一个例子。
假设我们有以下值列表
[3,2,9,11,7]
假设哈希表的大小为 7,我们将使用公式 (k1 % m),其中 m 是哈希表的大小。
下表显示了将生成的哈希值。
键 | 哈希算法 (k1 % m) | 哈希值 |
---|---|---|
3 | 3 % 7 | 3 |
2 | 3 % 7 | 2 |
9 | 3 % 7 | 2 |
11 | 3 % 7 | 4 |
7 | 3 % 7 | 0 |
从上述结果可以看出,值 2 和 9 具有相同的哈希值,我们不能在每个位置存储多个值。
给定的问题可以通过链式法或探测法来解决。以下各节详细讨论了链式法和探测法。
链式法
链式法是一种通过使用具有唯一索引的链表来解决冲突问题的方法。
下图可视化了链式列表的样子
2 和 9 都占据相同的索引,但它们被存储为链表。每个列表都有一个唯一的标识符。
链式列表的优点
以下是链式列表的优点
- 链式列表在插入数据时性能更好,因为插入的顺序是 O(1)。
- 使用链式列表的哈希表不需要调整大小。
- 只要有可用空间,它就可以轻松容纳大量值。
探测法
解决冲突的另一种技术是探测法。当使用探测法时,如果发生冲突,我们可以简单地继续查找一个空槽来存储我们的值。
以下是探测方法
方法 | 描述 |
---|---|
线性探测 | 顾名思义,此方法从发生冲突的位置开始向前搜索空槽。如果到达列表末尾且没有找到空槽。则从列表开头开始探测。 |
二次探测 | 此方法使用二次多项式表达式来查找下一个可用的空槽。 |
双重哈希 | 此技术使用二次哈希函数算法来查找下一个可用的空槽。 |
使用我们上面的例子,使用探测法后的哈希表将如下所示
哈希表操作
以下是哈希表支持的操作
- 插入 – 此操作用于将元素添加到哈希表
- 搜索 – 此操作用于使用键在哈希表中搜索元素
- 删除 – 此操作用于从哈希表中删除元素
插入数据操作
插入操作用于在哈希表中存储值。当在哈希表中存储新值时,会为其分配一个索引号。索引号是使用哈希函数计算的。哈希函数会解决计算索引号时发生的任何冲突。
搜索数据操作
搜索操作用于使用索引号在哈希表中查找值。搜索操作返回与搜索索引号相关联的值。例如,如果我们将在索引 2 处存储值 6,则索引号为 2 的搜索操作将返回值 6。
删除数据操作
删除操作用于从哈希表中删除值。要删除,操作是使用索引号完成的。一旦删除了一个值,该索引号就会被释放。它可以用于通过插入操作存储其他值。
使用 Python 示例实现哈希表
让我们看一个计算键的哈希值的简单示例
def hash_key( key, m): return key % m m = 7 print(f'The hash value for 3 is {hash_key(3,m)}') print(f'The hash value for 2 is {hash_key(2,m)}') print(f'The hash value for 9 is {hash_key(9,m)}') print(f'The hash value for 11 is {hash_key(11,m)}') print(f'The hash value for 7 is {hash_key(7,m)}')
哈希表代码解释
此处,
- 定义一个接受参数 key 和 m 的函数 hash_key。
- 使用简单的模运算来确定哈希值
- 定义一个变量 m,其值初始化为 7。这是我们哈希表的大小
- 计算并打印 3 的哈希值
- 计算并打印 2 的哈希值
- 计算并打印 9 的哈希值
- 计算并打印 11 的哈希值
- 计算并打印 7 的哈希值
执行上述代码将产生以下结果。
The hash value for 3 is 3 The hash value for 2 is 2 The hash value for 9 is 2 The hash value for 11 is 4 The hash value for 7 is 0
Python 字典示例
Python 带有一个内置数据类型,称为字典。字典是哈希表的一个示例。它使用键值对存储值。哈希值会自动为我们生成,任何冲突都会在后台为我们解决。
下面的示例演示了如何在 python 3 中使用字典数据类型
employee = { 'name': 'John Doe', 'age': 36, 'position': 'Business Manager.' } print (f"The name of the employee is {employee['name']}") employee['position'] = 'Software Engineer' print (f"The position of {employee['name']} is {employee['position']}") employee.clear() print (employee)
此处,
- 定义一个字典变量 employee。键 name 用于存储值 John Doe,age 存储 36,position 存储值 Business Manager。
- 检索键 name 的值并将其打印在终端上
- 将键 position 的值更新为 Software Engineer
- 打印键 name 和 position 的值
- 删除我们字典变量 employee 中存储的所有值
- 打印 employee 的值
运行上述代码将产生以下结果。
The name of the employee is John Doe. The position of John Doe is a Software Engineer. {}
复杂度分析
在最佳情况下,哈希表的平均时间复杂度为 O(1)。最坏情况下的时间复杂度为 O(n)。最坏情况发生在许多值生成相同的哈希键,并且我们必须通过探测来解决冲突。
实际应用
在实际应用中,哈希表用于存储数据
- 数据库
- 关联数组
- 集合
- 内存缓存
哈希表的优点
以下是使用哈希表的优点/好处
- 哈希表在查找数据、插入和删除现有值时具有高性能。
- 无论表中项目的数量如何,哈希表的时间复杂度都是恒定的。
- 即使处理大型数据集,它们的性能也非常出色。
哈希表的缺点
以下是使用哈希表的缺点
- 不能将 null 值用作键。
- 使用哈希函数生成键时,无法避免冲突。当生成已使用的键时,会发生冲突。
- 如果哈希函数存在许多冲突,这可能导致性能下降。
摘要
- 哈希表使用键值对存储数据。
- 哈希函数使用数学算法来计算哈希值。
- 当为多个值生成相同的哈希值时,会发生冲突。
- 链式法通过创建链表来解决冲突。
- 探测法通过在哈希表中查找空槽来解决冲突。
- 线性探测从发生冲突的槽开始,查找下一个空槽来存储值。
- 二次探测在发生冲突时使用多项式表达式查找下一个空槽。
- 双重哈希在发生冲突时使用二次哈希函数算法来查找下一个空槽。
- 与其他数据结构相比,哈希表具有更好的性能。
- 哈希表的平均时间复杂度为 O(1)
- Python 中的字典数据类型是哈希表的一个示例。
- 哈希表支持插入、搜索和删除操作。
- 不能将 null 值用作索引值。
- 哈希函数无法避免冲突。好的哈希函数可以最大限度地减少发生的冲突次数以提高性能。