文章目录
-
- 前言
- 参考目录
- 学习笔记
-
- 0:符号表(ST)实现小结
- 1:哈希函数
- 1.1:计算哈希函数
- 1.2:Java中的哈希码规范
- 1.2.1:哈希码实现:integers、booleans、doubles
- 1.2.2:哈希码实现:strings
- 1.2.3:哈希码实现:用户自定义类型
- 1.3:哈希码设计
- 1.4:模块化哈希
- 1.5:均匀哈希假设
- 2:分离链接 / 独立链表 Separate Chaining
- 2.1:哈希碰撞(哈希冲突)
- 2.2:分离链接 ST
- 2.3:Java 实现:查找、插入
- 2.4:分离链接分析
- 2.5:(补充)Java 实现:调整大小
- 2.6:(补充)Java 实现:删除
- 2.7:ST 实现小结
- 3:线性探测 Linear Probing
- 3.1:碰撞解决方案:开放式寻址
- 3.2:demo 演示:插入
- 3.3:demo 演示:搜索
- 3.4:线性探测小结
- 3.5:Java 实现:查找、插入
- 3.6:键簇
- 3.7:停车问题
- 3.8:线性探测分析
- 3.9:(补充)Java 实现:调整大小
- 3.10:(补充)Java 实现:删除
- 3.11:ST 实现小结
- 4:哈希背景
- 4.1 哈希算法变体
- 4.2 哈希表 vs 平衡查找树
前言
说起来挺丢脸的哈哈哈,在打开这一章之前,或者说是打开这一章节视频(官网)之前,我不知道 散列表
居然就是 Hash Tables
,也就是每一个会敲代码的人应该都用过的哈希表(属实是汗流浃背了朋友们)。所以我特意在本篇的标题后面打了个括号:(Hash Tables)
。
继续批评一下自己,说明基础太薄弱了,还得继续学习。不过说实话散列表说起来确实是不太习惯,所以 在本篇中还是尽量使用哈希来代替中文翻译的散列。
Ok,回到正题,本篇的主要内容包括:哈希函数、分离链接(Separate Chaining
,这个书里面中文翻译成 拉链法)、线性探测 以及一点点关于哈希的 背景(context
) 介绍。
参考目录
- B站 普林斯顿大学《Algorithms》视频课
(请自行搜索。主要以该视频课顺序来进行笔记整理,课程讲述的教授本人是该书原版作者之一 Robert Sedgewick。) - 微信读书《算法(第4版)》
(本文主要内容来自《3.4 散列表》) - 官方网站
(有书本配套的内容以及代码)
学习笔记
注1:下面引用内容如无注明出处,均是书中摘录。
注2:所有 demo 演示均为视频 PPT demo 截图。
注3:如果 PPT 截图中没有翻译,基本会在下面进行汉化翻译(除非很简单一眼懂就不翻译了),因为内容比较多,本文不再一一说明。
开篇的时候,Prof. Sedgewick 是这样介绍哈希表的:
Which is another approach to implementing symbol tables that can also be very effective in a practical applications.
这是实现符号表的另一种方法,在实际应用中也非常有效。
0:符号表(ST)实现小结
哈希表的简单引出:
将项目存储在 键索引表 中(索引是基于键的一个函数)。
哈希函数。用于从键计算数组索引的方法。
涉及的问题:
- 计算哈希函数(设计并实现一个有效的哈希函数以将键转化为数组索引。)
- 等价性测试:一种方法用于检查两个键是否相等。
- 冲突解决:当两个键通过哈希函数映射到同一个数组索引时,所采用的算法和数据结构来处理这种冲突。
经典的空间-时间权衡:
- 无空间限制情况:可以使用简单的哈希函数,直接将键作为数组索引。
- 无时间限制情况:可以采用顺序搜索进行最直观的冲突解决,无需特别的数据结构优化。
- 空间和时间受限情况:在实际应用中,需要兼顾时间和空间效率,这时就需要运用到真正的哈希技术。
1:哈希函数
1.1:计算哈希函数
理想目标:对键进行均匀随机打乱以生成表索引。
- 计算效率高。
- 对于每个键,每个表索引具有同等的概率被生成。(这是一个经过深入研究但仍存在实际应用问题的难题)
示例 1:电话号码。
- 不好:前三位。
- 更好:最后三位数字。
美国的电话前三位是区号,可以考虑日常生活中的电话号码,前三位一般是所属运营商,通常情况下会使用后四位数字进行简单区分(例如取快递报手机号后四位)。
示例 2:社会保险号。
- 不好:前三位。(573 = 加利福尼亚州,574 = 阿拉斯加(按地理区域内的时间顺序分配)
- 更好:最后三位数字。
书里面的相关描述:
实际挑战:每种密钥类型需要不同的方法。
1.2:Java中的哈希码规范
所有 Java 类都继承了一个方法 hashCode(),该方法返回一个 32 位整数。
要求:如果 x.equals(y)
,则 (x.hashCode() == y.hashCode())
。
高度期望:如果 !x.equals(y)
,则 (x.hashCode() != y.hashCode())
。
默认实现:返回对象x的内存地址。
合法(但较差)的实现:始终返回 17。
定制化实现示例:Integer、Double、String、File、URL、Date 等。
用户自定义类型:对于用户自定义的类型(需要自行设计和实现)。
1.2.1:哈希码实现:integers、booleans、doubles
找了一下 jdk 包的源码:
Oracle OpenJDK version 11.0.6
java.lang.Integer#hashCode
java.lang.Boolean#hashCode
java.lang.Double#hashCode
1.2.2:哈希码实现:strings
霍纳法则(Horner’s method)用于计算长度为L的字符串哈希值:需要进行L次乘法和加法操作。
同样地,找了一下 jdk 包的源码(不过版本差异因此实现有出入,这里看一下 jdk 11 以及 jdk 8 的实现):
Oracle OpenJDK version 11.0.6
java.lang.String#hashCode
jdk 11 区分了不同的字符集 StringUTF16
以及 StringLatin1
:
java.lang.StringUTF16#hashCode
java.lang.StringLatin1#hashCode