哈希表的概念

哈希表的基本概念

哈希表的定义

哈希表也称散列表,是一种以键值对形式存储记录的数据结构,该数据结构支持根据键的内容直接访问在内存特定位置的值,并且可以进行查找、添加和删除操作。

哈希表的原理是将键通过函数映射到内存特定位置,加快操作速度,该函数称为哈希函数或散列函数。

理想情况下,哈希表的每次操作的时间复杂度是 O ( 1 ) O(1) O(1)

哈希函数

哈希函数的作用是将键映射到索引。哈希函数的设计很重要,决定了哈希表的时间与空间的平衡。

哈希函数应满足容易计算且能够将所有的键均匀分布。理想情况下,不同的键应该映射到不同的索引,但是实际情况下,因为哈希函数的设计和索引空间限制等因素,可能出现不同的键映射到相同的索引的情况,称为哈希冲突。

哈希冲突

不同的键映射到相同的索引称为哈希冲突。哈希冲突的概率和哈希函数的设计有直接关系,好的哈希函数可以显著减少哈希冲突的情况,但是很难完全避免哈希冲突。因此,需要有解决哈希冲突的方法。

有两种常见的解决哈希冲突的方法,一是链地址法,二是开放寻址法。

链地址法

链地址法也称拉链法,其思想是将映射到相同索引的值存入同一个链表中。

链地址法解决哈希冲突的做法简单,查找、添加和删除操作的实现都较为简单,虽然链地址法的各项操作的时间复杂度在最坏情况下会退化为线性,但是在平均情况下仍然是 O ( 1 ) O(1) O(1)

开放寻址法

开放寻址法的思想是:当发生哈希冲突时,继续探查哈希表中的其他索引位置,直到找到空的索引位置用于填入值。

和链地址法相比,开放寻址法的删除操作更为复杂。为了避免在删除之后导致重新寻址的键无法找到,进行删除操作时,只能在被删除的位置做删除标记而不能真正删除。

开放寻址法的常用探查做法有三种:线性探查、二次探查和双重哈希。

假设哈希表有 m m m 个槽位,定义辅助哈希函数 h h h,其值域为从 0 0 0 m − 1 m - 1 m1 的全部整数。

线性探查采用的哈希函数是 f ( x , i ) = ( h ( x ) + i )   m o d   m f(x, i) = (h(x) + i) \bmod m f(x,i)=(h(x)+i)modm,其中 i i i 是整数且 0 ≤ i < m 0 \le i < m 0i<m。线性探查从特定槽位开始依次遍历每个槽位,直到找到未被占用的槽位。线性探查的实现简单,但是当被占用的槽位增加时,线性探查的平均探查时间会增加,导致性能受到影响。

二次探查采用的哈希函数是 f ( x , i ) = ( h ( x ) + c 1 × i + c 2 × i 2 )   m o d   m f(x, i) = (h(x) + c_1 \times i + c_2 \times i^2) \bmod m f(x,i)=(h(x)+c1×i+c2×i2)modm,其中 i i i 是整数且 0 ≤ i < m 0 \le i < m 0i<m c 1 c_1 c1 c 2 c_2 c2 是正的辅助常数。二次探查的效果优于线性探查,但是效果取决于 c 1 c_1 c1 c 2 c_2 c2 m m m 的取值。

双重哈希采用的哈希函数是 f ( x , i ) = ( h 1 ( x ) + h 2 ( x ) × i )   m o d   m f(x, i) = (h_1(x) + h_2(x) \times i) \bmod m f(x,i)=(h1(x)+h2(x)×i)modm,其中 h 1 h_1 h1 h 2 h_2 h2 是两个辅助哈希函数, i i i 是整数且 0 ≤ i < m 0 \le i < m 0i<m。为了探查到整个哈希表, h 2 ( x ) h_2(x) h2(x) 的值必须和 m m m 互质。

三种探查做法中,双重哈希的效果最好。

Java 中的哈希表和哈希集合

Map \texttt{Map} Map HashMap \texttt{HashMap} HashMap TreeMap \texttt{TreeMap} TreeMap

Map \texttt{Map} Map 接口表示用于存储键值对映射关系的数据结构,其中的任意两个键值对的键都不相同。 Map \texttt{Map} Map 接口需要通过实现类实例化,常见的实现类包括 HashMap \texttt{HashMap} HashMap 类和 TreeMap \texttt{TreeMap} TreeMap 类。

HashMap \texttt{HashMap} HashMap 类是哈希映射的类,其底层实现是数组。解决哈希冲突的方法是链地址法,具体做法在不同 JDK 版本中有所不同。

  • 在 JDK 1.7 及之前的版本中, HashMap \texttt{HashMap} HashMap 类的实现为数组 + 单向链表,即数组中的每个元素都是一个单向链表。
  • 在 JDK 1.8 及之后的版本中, HashMap \texttt{HashMap} HashMap 类的实现为数组 + 单向链表 + 红黑树,当链表长度过大时,将链表转化为红黑树以提升效率。链表和红黑树的切换规则如下:
    • 如果链表的长度大于 8 8 8,则将链表转化为红黑树;
    • 如果红黑树的结点数小于 6 6 6,则将红黑树转化回链表。

HashMap \texttt{HashMap} HashMap 的各项操作的平均时间复杂度是 O ( 1 ) O(1) O(1),但是其中的键值对是无序的。

TreeMap \texttt{TreeMap} TreeMap 类是有序映射的类,其底层实现是红黑树。 TreeMap \texttt{TreeMap} TreeMap 类可以确保其中的键值对是按照键的大小排序的。由于需要维护有序性, TreeMap \texttt{TreeMap} TreeMap 的各项操作的时间复杂度是 O ( log ⁡ n ) O(\log n) O(logn),其中 n n n TreeMap \texttt{TreeMap} TreeMap 存储的键值对数目。

HashMap \texttt{HashMap} HashMap 在 JDK 1.8 及之后版本的实现和 TreeMap \texttt{TreeMap} TreeMap 的实现都使用了红黑树的数据结构。红黑树是一种自平衡二叉搜索树,对于 n n n 个结点的红黑树,其查找、插入和删除操作的时间复杂度都是 O ( log ⁡ n ) O(\log n) O(logn)。红黑树将在树、二叉树和二叉搜索树的部分介绍。

Set \texttt{Set} Set HashSet \texttt{HashSet} HashSet TreeSet \texttt{TreeSet} TreeSet

Set \texttt{Set} Set 接口表示用于存储无重复元素的数据结构,其中的任意两个元素都不相同,同一个元素不能被重复加入。 Set \texttt{Set} Set 接口需要通过实现类实例化,常见的实现类包括 HashSet \texttt{HashSet} HashSet 类和 TreeSet \texttt{TreeSet} TreeSet 类。

HashSet \texttt{HashSet} HashSet 类是哈希集合的类,其实现基于 HashMap \texttt{HashMap} HashMap 类。 HashSet \texttt{HashSet} HashSet 的各项操作的平均时间复杂度是 O ( 1 ) O(1) O(1),但是其中的元素是无序的。

TreeSet \texttt{TreeSet} TreeSet 类是有序集合的类,其实现基于 TreeMap \texttt{TreeMap} TreeMap 类。 TreeSet \texttt{TreeSet} TreeSet 中的元素是有序的,各项操作的时间复杂度是 O ( log ⁡ n ) O(\log n) O(logn),其中 n n n TreeSet \texttt{TreeSet} TreeSet 存储的元素数目。

哈希表的应用场景和使用技巧

哈希表的应用主要有以下场景:

  1. 计数,使用哈希表记录元素的次数;
  2. 存储已经计算过的信息,避免重复计算,在动态规划中经常会使用哈希表存储已经计算过的信息;
  3. 判断一个元素是否已经访问过,该场景常用哈希集合,判断链表是否有环问题和搜索问题中经常使用哈希集合;
  4. 和双向链表结合,可以实现最近最少使用(LRU)缓存和最不经常使用(LFU)缓存。

大多数情况下,使用哈希表和哈希集合的场景都会使用 HashMap \texttt{HashMap} HashMap 类和 HashSet \texttt{HashSet} HashSet 类的对象。如果哈希表的键范围有限或者哈希集合的元素范围有限,例如只能是数字或者只能是字母,则可以用数组代替哈希表。虽然从复杂度分析的角度而言,数组和哈希表的时间复杂度和空间复杂度相同,但是在实际运行时,数组的操作时间和占用空间都优于哈希表。

目录

  1. 哈希表题目:两数之和
  2. 哈希表题目:唯一摩尔斯密码词
  3. 哈希表题目:快乐数
  4. 哈希表题目:在长度 2N 的数组中找出重复 N 次的元素
  5. 哈希表题目:有效的数独
  6. 哈希表题目:键盘行
  7. 哈希表题目:宝石与石头
  8. 哈希表题目:赎金信
  9. 哈希表题目:“气球”的最大数量
  10. 哈希表题目:相交链表
  11. 哈希表题目:环形链表
  12. 哈希表题目:环形链表 II
  13. 哈希表题目:整数转罗马数字
  14. 哈希表题目:罗马数字转整数
  15. 哈希表题目:同构字符串
  16. 哈希表题目:单词规律
  17. 哈希表题目:查找和替换模式
  18. 哈希表题目:字符串中的第一个唯一字符
  19. 哈希表题目:公平的糖果交换
  20. 哈希表题目:找出数组中的幸运数
  21. 哈希表题目:独特的电子邮件地址
  22. 哈希表题目:验证外星语词典
  23. 哈希表题目:查找共用字符
  24. 哈希表题目:两个相同字符之间的最长子字符串
  25. 哈希表题目:最短补全词
  26. 哈希表题目:亲密字符串
  27. 哈希表题目:判断路径是否相交
  28. 哈希表题目:矩阵置零
  29. 哈希表题目:设计哈希集合
  30. 哈希表题目:设计哈希映射
  31. 哈希表题目:从英文中重建数字
  32. 哈希表题目:猜数字游戏猜数字游戏策略实现
  33. 哈希表题目:数组中的 k-diff 数对
  34. 哈希表题目:数组的度
  35. 哈希表题目:单词子集
  36. 哈希表题目:子域名访问计数
  37. 哈希表题目:字母板上的路径
  38. 哈希表题目:制造字母异位词的最小步骤数
  39. 哈希表题目:数的平方等于两数乘积的方法数
  40. 哈希表题目:砖墙
  41. 哈希表题目:森林中的兔子
  42. 哈希表题目:保证文件名唯一
  43. 哈希表题目:最长连续序列
  44. 哈希表题目:在系统中查找重复文件
  45. 哈希表题目:数组中重复的数据
  46. 哈希表题目:四数相加 II
  47. 哈希表题目:网格照明
  48. 哈希表题目:从链表中删去总和值为零的连续结点
  49. 哈希表题目:设计地铁系统
  50. 哈希表题目:缺失的第一个正数
  51. 哈希表题目:元音拼写检查器
  52. 哈希表题目:TinyURL 的加密与解密
  53. 哈希表题目:最大频率栈
  54. 哈希表题目:设计推特
  55. 哈希表题目:LRU 缓存
  56. 哈希表题目:最大相等频率
  57. 哈希表题目:原子的数量
  58. 哈希表题目:LFU 缓存
  59. 哈希表题目:Lisp 语法解析
  60. 哈希表题目:全 O(1) 的数据结构
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值