【数据结构】哈希表Hash Table

1. 什么是哈希表/散列表

哈希表(也称散列表)简单来说是构造了一种映射,它是根据关键码值(Key value)而直接进行访问的数据结构。我们可以把一维数组看作是一种简单的哈希表。

它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

散列函数可能会把两个不同的key映射到同一地址,此时就产生了冲突,这些发生碰撞的不同关键字key我们就成为同义词

在这里插入图片描述

2. 哈希函数的构造方法

构造哈希函数的注意事项:

  1. 散列函数的定义域应包含全部的关键字key,值域取决于散列表的大小或地址的范围
  2. 散列函数计算出的地址应能等概率的、均匀的分布在整个地址空间,以减少冲突的发生
  3. 散列函数应尽量简单,以便在较短时间内计算出任意关键字对应的存储地址

2.1 直接定址法(使用较多)

直接取关键字的某个线性函数值为散列地址,散列函数为 H ( k e y ) = a ∗ k e y + b H(key)=a*key+b H(key)=akey+b,其中a和b都为常数。
优点:简单,并且不会产生冲突

2.2 除留余数法(使用较多)

假定散列表表长为m,散列函数 H ( k e y ) = k e y % p H(key)=key\%p H(key)=key%p,其中p为不大于m但最接近或等于m的质数
除留余数法的关键是选好p

2.3 数字分析法

假设关键字集合中的每个关键字key都是由s位数字组成 ( k 1 , k 2 , k 3 . . . . . . . k n ) (k_1,k_2,k_3.......k_n) (k1,k2,k3.......kn) ,分析key中的全体数据,并从中提取分布均匀的若干位或他们的组合构成全体

例如对于某公司员工的电话号码,前七位都是非常相似的,第一位都是1、第二位都是3…,即数字分布不均匀(1、3出现的最多,而其他数字出现的少,若以此建立映射关系容易出现冲突)
而后四位分布较为均匀,因此选择后四位作为散列地址较好
在这里插入图片描述
适用情况此种方法通常用于数字位数较长的情况,必须数字存在一定规律,其必须知道数字的分布情况

2.4 平方取中法

关键字的平方值的中间几位作为散列地址,具体取多少位视实际情况而定。这种方法得到的散列地址与关键字的每一位都有关系,使得散列地址分布较为均匀。
适用情况:这种方法适合事先不知道数据并且数据长度较小的情况

例如:
在这里插入图片描述

2.5 折叠法

将关键字分割成位数相同的几部分(最后一部分的位数可能短一些),然后将这几部分的叠加和作为散列地址。

适用情况:关键字位数很多,而且关键字中每一位上数字分布大致均匀
在这里插入图片描述

3. 冲突的解决方案

3.1 开放地址法

开放地址法:将产生冲突的hash地址作为自变量,通过某种冲突解决函数得到一个新的空闲的hash地址

3.1.1 线性探测法

线性探测法:冲突发生时,顺序查看表中下一个单元(当探测到表尾地址m-1时,下一个探测地质为表首地址0),直到找出一个空闲单元。

线性探测法肯定能找到一个空闲单元

举例: H ( k e y ) = k e y % 8 H(key)=key\%8 H(key)=key%8,待处理的元素有[9,10,11,19,27]
第一步: 9 % 8 = 1 9\%8=1 9%8=1

下标012345678
key9

第二步: 10 % 8 = 2 10\%8=2 10%8=2

下标012345678
key910

第三步: 11 % 8 = 3 11\%8=3 11%8=3

下标012345678
key91011

第四步: 19 % 8 = 3 19\%8=3 19%8=3

下标012345678
key9101119

第五步: 27 % 8 = 3 27\%8=3 27%8=3

下标012345678
key910111927

线性探测方法的局限性:会造成大量元素在相邻的散列地址上“聚集”起来,降低查找效率

3.1.2 平方探测法

平方探测法:设发生冲突的地址为d,平方探测法得到的新的地址序列为 d + 1 2 、 d − 1 2 、 d + 2 2 、 d − 2 2 、 d + 3 2 、 d − 3 2 . . . . . d+1^2、d-1^2、d+2^2、d-2^2、d+3^2、d-3^2..... d+12d12d+22d22d+32d32.....

平方探测法的优势:可以避免出现堆积问题

平方探测法的局限性:加减d的平方跨度太大,不能探测到散列表的所有单元,容易造成空闲单元的浪费,但至少能探测到一半的单元

3.1.3 再散列法

再散列法:又称双散列法。需要使用两个散列函数,当通过第一个散列函数 H ( k e y ) H(key) H(key)得到的地址发生冲突时,则使用第二个散列函数 H 2 ( k e y ) H2(key) H2(key)计算该关键字的地址增量

举例:
H ( k e y ) = k e y % 8 H(key)=key\%8 H(key)=key%8 H a s h 2 ( k e y ) = k e y % 2 Hash2(key)=key\%2 Hash2(key)=key%2,则最终第 i i i 个元素的散列地址 H i = ( H ( k e y ) + i ∗ H a s h 2 ( k e y ) ) % m H_i=(H(key)+i*Hash2(key))\%m Hi=(H(key)+iHash2(key))%m,其中m是散列表的长度,i是冲突次数,i的初值=0

比如对于前面例子的第四步,插入19时, 19 % 8 = 3 19\%8=3 19%8=3,产生了第一次冲突,则计算新的哈希值 H i = ( 3 + 1 ∗ 1 ) % 9 = 4 H_i=(3+1*1)\%9=4 Hi=(3+11)%9=4,于是将19插入下标为4的位置

第四步: H ( k e y ) 19 % 8 = 3 , i = 1 , H i = ( 3 + 1 ∗ 1 ) % 9 = 4 H(key)19\%8=3,i=1,H_i=(3+1*1)\%9=4 H(key)19%8=3i=1Hi=(3+11)%9=4

下标012345678
key9101119

3.1.4 伪随机序列法

伪随机序列法:当发生冲突时,地址增量为伪随机数序列

3.2 拉链法

拉链法:把所有同义词存储在一个线性链表中,这个线性链表由其散列地址唯一标识

拉链法适用情况:适用于经常进行插入和删除的情况

例题

4. 复杂度分析

散列表的查找性能:和装填因子有关

装填因子:记为 α \alpha α,表示一个表的装满程度。

α = 表 中 记 录 数 n 散 列 表 长 度 m \alpha=\frac{表中记录数n}{散列表长度m} α=mn

散列表的平均查找长度依赖于散列表的装填因子 α \alpha α,而不直接依赖于n或m

α \alpha α越大,表示装填记录越满,发生冲突的可能性就越大

搜索复杂度:当没有碰撞时,搜索的复杂度为 O ( 1 ) O(1) O(1);当有碰撞时,搜索的复杂度为 O ( K ) O(K) O(K),其中K为冲突的个数

插入复杂度 O ( 1 ) O(1) O(1)

删除复杂度 O ( 1 ) O(1) O(1)

5. 例题

在这里插入图片描述
查找成功时的复杂度:
在这里插入图片描述

计算查找成功的复杂度时,可以水平画一条直线,第一条直线上有7个元素,这7个都是只查一次就可以查到的,因此为1 * 7。
接着画第二条直线,第二条直线上有2个元素,这2个都是只查一次就可以查到的,因此为2 * 2。

全部计算完成后累计加和,再除13

查找失败时的复杂度:
在这里插入图片描述

查找失败时的复杂度即为每条链表上结点个数的和/13

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
哈希表是一种基于哈希函数进行快速查找的数据结构,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。哈希表的设计思路如下: 1. 哈希函数的设计:哈希函数是哈希表的核心,它将关键字映射到哈希表中的位置。一个好的哈希函数应该具有以下特点: - 映射范围广:哈希函数应该将关键字均匀地映射到哈希表中的位置,避免出现大量的哈希冲突。 - 计算速度快:哈希函数的计算速度应该尽可能快,以提高哈希表的访问速度。 - 低冲突率:哈希函数应该尽可能地避免哈希冲突,以提高哈希表的访问效率。 2. 哈希冲突的解决:由于哈希函数的映射范围是有限的,所以不同的关键字可能会映射到同一个位置,这就是哈希冲突。哈希冲突的解决方法有以下两种: - 链地址法:将哈希表中的每个位置都连接一个链表,当发生哈希冲突时,将新的关键字插入到链表的末尾。 - 开放地址法:当发生哈希冲突时,通过某种算法找到哈希表中的下一个空位置,将新的关键字插入到该位置。 3. 哈希表的增删查改操作:哈希表的增删查改操作都需要先通过哈希函数找到关键字在哈希表中的位置,然后再进行相应的操作。具体操作如下: - 插入操作:将新的关键字插入到哈希表中的对应位置,如果发生哈希冲突,则按照链地址法或开放地址法进行解决。 - 删除操作:将关键字从哈希表中对应位置删除,如果该位置上有链表,则需要遍历链表找到对应的关键字进行删除。 - 查找操作:通过哈希函数找到关键字在哈希表中的位置,如果该位置上有链表,则需要遍历链表找到对应的关键字进行查找。 - 修改操作:通过哈希函数找到关键字在哈希表中的位置,如果该位置上有链表,则需要遍历链表找到对应的关键字进行修改。 下面是一个使用链地址法实现的哈希表的Python代码示例: ```python class ListNode: def __init__(self, key=None, value=None): self.key = key self.value = value self.next = None class MyHashMap: def __init__(self): self.size = 1000 self.table = [None] * self.size def _hash(self, key): return key % self.size def put(self, key, value): index = self._hash(key) if not self.table[index]: self.table[index] = ListNode(key, value) else: node = self.table[index] while node: if node.key == key: node.value = value return if not node.next: break node = node.next node.next = ListNode(key, value) def get(self, key): index = self._hash(key) node = self.table[index] while node: if node.key == key: return node.value node = node.next return -1 def remove(self, key): index = self._hash(key) node = prev = self.table[index] if not node: return if node.key == key: self.table[index] = node.next else: node = node.next while node: if node.key == key: prev.next = node.next break node, prev = node.next, prev.next ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值