CS61B 课程笔记(Lecture 23 Hashing)
CS61B 课程笔记(Lecture 23 Hashing)
哈希基础
哈希代码
定义:哈希代码是用于确定哈希表中索引的值。
计算方法:在Java中,可以使用以下代码计算哈希表的索引:
1
int index = Math.floorMod(key.hashCode(), array.length);
- 这里
array
是表示哈希表的底层数组。 - 说明:
Math.floorMod
函数用于计算模,并能够正确处理负整数。%
运算符在处理负数时可能会得到负结果,这可能导致索引错误。
- 这里
有效哈希码的定义
有效哈希码的两个属性
确定性:
当两个对象A和B相等(
A.equals(B) == true
)时,它们的哈希码必须相同。这意味着哈希函数不能依赖于equals
方法中未反映的属性。代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof MyObject)) return false;
MyObject other = (MyObject) obj;
return this.field.equals(other.field);
}
public int hashCode() {
return field.hashCode(); // 确保与equals一致
}
一致性:
哈希码函数每次调用同一对象实例时必须返回相同的整数。哈希码函数应独立于时间、随机数生成器等因素。
代码示例:
1
2MyObject obj = new MyObject("example");
System.out.println(obj.hashCode()); // 每次调用应返回相同值
良好哈希码的特性
良好哈希码的属性
哈希码必须有效。
哈希码的值应均匀分布在所有整数集上,以减少冲突。
哈希码计算应快速,理想情况下为O(1)常数时间复杂度。
实现示例:
1
2
3
4
public int hashCode() {
return 31 * field1.hashCode() + field2.hashCode(); // 示例的良好哈希实现
}
处理冲突:线性探测与外部链接
冲突
- 当多个元素在数组中具有相同的索引时发生冲突。
两种常见的处理冲突的方法
线性探测:
将冲突的键存储在数组的下一个空位中。
代码示例:
1
2
3
4
5
6
7public void put(K key, V value) {
int index = Math.floorMod(key.hashCode(), array.length);
while (array[index] != null) { // 找到下一个空位
index = (index + 1) % array.length;
}
array[index] = new Entry<>(key, value);
}
外部链接:
将所有具有相同哈希值的键存储在一个集合中,如链表。这样的集合称为“桶”。
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13class Bucket {
List<Entry<K, V>> entries = new LinkedList<>();
}
public void put(K key, V value) {
int index = Math.floorMod(key.hashCode(), array.length);
Bucket bucket = array[index];
if (bucket == null) {
bucket = new Bucket();
array[index] = bucket;
}
bucket.entries.add(new Entry<>(key, value)); // 添加到链表中
}
哈希表的调整与性能
扩展哈希表
- 如果哈希表的底层数组较小且插入许多键,会导致更多冲突,因此应在达到一定负载因子时扩展数组。
- 负载因子:插入的元素数量与数组总长度的比率。
重要方程
定义最大负载因子。如果插入后负载因子超出最大值,哈希表应调整大小,通常是将数组长度加倍。
代码示例:
1
2
3
4
5
6
7
8
9
10
11public void put(K key, V value) {
if (loadFactor() > MAX_LOAD_FACTOR) {
resize(); // 调整大小
}
// 插入逻辑
}
private void resize() {
// 复制旧数组到新数组
// 调整哈希值并重新插入
}
示例
- 假设哈希表的数组长度为10,当前包含7个元素,负载因子为0.7。插入另一个元素后,负载因子将达到0.8,超出最大负载因子,因此需要将数组长度调整为20。
总结
- 本章介绍了哈希的强大技术,如何将复杂对象(如字符串)转化为数值(如整数)。
- 哈希表结合了哈希函数和常数时间索引的数组特性,构建HashMap允许以摊销常数时间访问任何键值对。
- 本实现的缺点包括表示不同对象的能力、内存效率和冲突问题。
- 讨论了哈希码函数的重要性及其对哈希表运行时的影响,以及如何通过外部链接解决冲突,探索了基于负载因子的动态调整功能。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Totoroの旅!
评论