哈希表
哈希表名字源于 Hash,也可以叫作散列表。哈希表是一种特殊的数据结构,它与数组、链表以及树等我们之前学过的数据结构相比,有很明显的区别。
哈希表的核心思想
哈希表的设计采用了函数映射的思想,将记录的存储位置与记录的关键字关联起来。这样的设计方式,能够快速定位到想要查找的记录,而且不需要与表中存在的记录的关键字比较后再来进行查找
简单理解为 地址 = f(关键字)
通过构建哈希表,直接构建关键字到地址的映射关系,查询可以降低时间复杂度,仅为O(1),之前的其他数据类型查询的时间复杂度为O(n)
哈希函数的致命问题——哈希冲突
假如Hash函数为每个字的开头大写字母的ASCII码之和
address (张一) = ASCII (Z) + ASCII (Y) = 90 + 89 = 179;
address (张二) = ASCII (Z) + ASCII (E) = 90 + 69 = 159;
address (张三) = ASCII (Z) + ASCII (S) = 90 + 83 = 173;
address (张四) = ASCII (Z) + ASCII (S) = 90 + 83 = 173;
f(张三)和f(张四)都是173,这个现象就叫哈希冲突
从本质上来看,哈希冲突只能尽量减少,不能完全避免。因为只要输入的数据量够多,分布够广,就可能发生冲突。
因此,哈希表需要设计合理的哈希函数,并且对冲突有一套处理机制。
设计哈希表
常用设计方法:
- 直接定址法
哈希函数为关键字到地址的线性函数。如,H (key) = a*key + b (a、b为常数)
- 数字分析法
假设关键字集合中的每个关键字 key 都是由 s 位数字组成(k1,k2,…,Ks),并从中提取分布均匀的若干位组成哈希地址
- 平方取中法
如果关键字的每一位都有某些数字重复出现,并且频率很高,我们就可以先求关键字的平方值,通过平方扩大差异,然后取中间几位作为最终存储地址
- 折叠发
如果关键字的位数很多,可以将关键字分割为几个等长的部分,取它们的叠加和的值(舍去进位)作为哈希地址
- 除留余数法
预先设置一个数 p,然后对关键字进行取余运算。即地址为 key mod p
哈希冲突的解决方法
- 开放定址法
即当一个关键字和另一个关键字发生冲突时,使用某种探测技术在哈希表中形成一个探测序列,然后沿着这个探测序列依次查找下去。当碰到一个空的单元时,则插入其中。
常用的探测方法是线性探测法。 比如有一组关键字 {12,13,25,23},采用的哈希函数为 key mod 11。当插入 12,13,25 时可以直接插入,地址分别为 1、2、3。而当插入 23 时,哈希地址为 23 mod 11 = 1。然而,地址 1 已经被占用,因此沿着地址 1 依次往下探测,直到探测到地址 4,发现为空,则将 23 插入其中。
- 链地址法
将哈希地址相同的记录存储在一张线性链表中。
例如,有一组关键字 {12,13,25,23,38,84,6,91,34},采用的哈希函数为 key mod 11。
哈希表优势:提供快速的插入、删除、查找操作。无论多少数据,插入和删除值都接近常量的时间,在查找方面,哈希表速度比树还快。
哈希表不足:没有顺序概念,不能以固定方式遍历元素(从大到小等)。在数据处理顺序敏感的问题,哈希表不是好方法,且哈希表的key是不允许重复的,在重复性高的数据中,哈希表也不是好方法
哈希表的基本操作
在很多高级语言中,哈希函数、哈希冲突都已经在底层完成了黑盒化处理,是不需要开发者自己设计的。也就是说,哈希表完成了关键字到地址的映射,可以在常数级时间复杂度内通过关键字查找到数据。
哈希表查找的细节过程是:对于给定的 key,通过哈希函数计算哈希地址 H (key)。
如果哈希地址对应的值为空,则查找不成功。
反之,则查找成功。
虽然哈希表查找的细节过程还比较麻烦,但因为一些高级语言的黑盒化处理,开发者并不需要实际去开发底层代码,只要调用相关的函数就可以了。
例:
1.将关键字序列 {7, 8, 30, 11, 18, 9, 14} 存储到哈希表中。哈希函数为: H (key) = (key * 3) % 7,处理冲突采用线性探测法。
H (7) = (7 * 3) % 7 = 0
H (8) = (8 * 3) % 7 = 3
H (30) = 6
H (11) = 5
H (18) = 5
H (9) = 6
H (14) = 0
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Key | 7,14 | 8 | 11,18 | 30,9 |
按照关键字序列以此向哈希表中填入,发生冲突过按照线性探测探测到的第一个空位置填入
最终结果为
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Key | 7 | 14 | 8 | 11 | 30 | 18 | 9 |
查找流程
找7:H(7)=0,根据哈希表在0的位置,匹配,查找完成
找18:H(18)=5,根据哈希表在5的位置,得到结果11,不匹配,然后挪一位,在6的位置得到30,不匹配,再往后挪,在7的位置的的带18,匹配,查找完成。
2.假设有一个在线系统,可以实时接收用户提交的字符串型关键字,并实时返回给用户累积至今这个关键字被提交的次数。
例:用户输入"abc",系统返回 1。用户再输入"jk",系统返回 1。用户再输入"xyz",系统返回 1。用户再输入"abc",系统返回 2。用户再输入"abc",系统返回 3。
采用哈希表,(哈希表新增、查找常数级时间复杂度O(1))。预先定义好哈希表,输入字符串判断是哈希表中否存在记录
- 如果存在,对应的value加1
- 如果不存在,增加到哈希表中,value赋值1,打印value
if (d.containsKey(key_str) {
d.put(key_str, d.get(key_str) + 1);
}
else{
d.put(key_str, 1);
}
System.out.println(d.get(key_str));