数据结构基础笔记(9)哈希表

哈希表:如何利用好高效率查找的利器
1.什么是哈希表?
哈希表名字源于Hash,也叫做散列表
哈希表是一种特殊的数据结构
它与数组、链表以及树等数据结构相比,有很明显的区别。
2哈希表的核心思想
数据的存储位置和数据的具体数值之间不存在任何关系
在面对查找问题时,这些数据结构必须采用逐一比较的方法去实现
哈希表的设计采用了函数映射的思想,将记录的存储位置与记录的关键字关联起来
这样的设计方式,能够快速定位到想要查找的记录
而且不需要与表中存在的记录的关键字比较后再来进行查找
数组使用过数据的索引来取出数值的。通过这样的方式,数组实现了"地址=f(index)“的映射关系
如果用哈希表的逻辑来理解,这里的f()就是一个哈希函数
他完成了索引值到实际地址的映射,这让数组可以快速完成基于索引值的查找
数组的局限性在于,只能基于数据的索引去查找,而不能基于数据的数值去查找
哈希表需要设计合理的哈希函数并对冲突有一定的机制。
3.如何设计哈希函数
1.直接定制法:
哈希函数为关键字到地址的线性函数。
2.数字分析法
假设关键字集合的每个关键字key都是由s位数字组成(k1,k2,…ks)
并从中提取分布均匀的若干位组成哈希地址
3.平方取中法
如果关键字的每一位都有某些数字重复出现,并且频率很高
可以先求关键字的平方值,通过平方扩大差异,然后取中间几位作为最终存储地址
4.折叠法
如果关键字的位数很多,可以将关键字分割为几个等长的部分
取它们的叠加和的值(舍去进位)作为哈希地址
5.除留余数法
预先设置一个数p,然后对关键字进行取余运算。即地址为key mod p
4如何解决哈希冲突
1.开放地址法:
当一个关键字和另一个关键字发生冲突时,使用某种探测技术在哈希表中形成一个探测序列,然后沿着这个探测序列依次查找下去,当碰到一个空的单元时,则插入其中,常用的探测方法是线性探测法
2.链地址法:
将哈希地址相同的记录存储在一张线性链表中。
5哈希表的优劣
优势
它可以提供非常快速的插入-删除-查找操作,无论多少数据,插入和删除值需要接近常量的时间
在查找方面,哈希表的速度比树还要快,基本可以瞬间查找到想要的元素
不足
哈希表中的数据是没有顺序概念的,所以不能以一种固定的方式(比如从小到大)来遍历其中的元素
哈希表中的key是不允许重复的
6哈希表的基本操作
在很多高级语言中,哈希函数、哈希冲突都已经在底层完成了黑盒化处理,是不需要开发者自己设计的。也就是说,哈希表完成了关键字到地址的映射,可以在常数级时间复杂度内通过关键字查找到数据。
至于实现细节,比如用了哪个哈希函数,用了什么冲突处理,甚至某个数据记录的哈希地址是多少,都是不需要开发者关注的。接下来,我们从实际的开发角度,来看一下哈希表对数据的增删查操作。
哈希表中的增加和删除数据操作,不涉及增删后对数据的挪移问题(数组需要考虑),因此处理就可以了。
哈希表查找的细节过程是:对于给定的 key,通过哈希函数计算哈希地址 H (key)。
如果哈希地址对应的值为空,则查找不成功。
反之,则查找成功。
虽然哈希表查找的细节过程还比较麻烦,但因为一些高级语言的黑盒化处理,开发者并不需要实际去开发底层代码,只要调用相关的函数就可以了。
7哈希表的案例
例 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
按关键字序列顺序依次向哈希表中填入,发生冲突后按照“线性探测”探测到第一个空位置填入。
有了这个表之后,我们再来看一下查找的流程:
查找 7。输入 7,计算得到 H (7) = 0,根据哈希表,在 0 的位置,得到结果为 7,跟待匹配的关键字一样,则完成查找。
查找 18。输入 18,计算得到 H (18) = 5,根据哈希表,在 5 的位置,得到结果为 11,跟待匹配的关键字不一样(11 不等于 18)。因此,往后挪移一位,在 6 的位置,得到结果为 30,跟待匹配的关键字不一样(11 不等于 30)。因此,继续往后挪移一位,在 7 的位置,得到结果为 18,跟待匹配的关键字一样,完成查找。
例 2,假设有一个在线系统,可以实时接收用户提交的字符串型关键字,并实时返回给用户累积至今这个关键字被提交的次数。
例如,用户输入"abc”,系统返回 1。用户再输入"jk",系统返回 1。用户再输入"xyz",系统返回 1。用户再输入"abc",系统返回 2。用户再输入"abc",系统返回 3。
一种解决方法是,用一个数组保存用户提交过的所有关键字。当接收到一个新的关键字后,插入到数组中,并且统计这个关键字出现的次数。
根据数组的知识可以计算出,插入到最后的动作,时间复杂度是 O(1)。但统计出现次数必须要全部数据遍历一遍,时间复杂度是 O(n)。随着数据越来越多,这个在线系统的处理时间将会越来越长。显然,这不是一个好的方法。
如果采用哈希表,则可以利用哈希表新增、查找的常数级时间复杂度,在 O(1) 时间复杂度内完成响应。预先定义好哈希表后(可以采用 Map < String, Integer > d = new HashMap <> (); )对于关键字(用变量 key_str 保存),判断 d 中是否存在 key_str 的记录。
如果存在,则把它对应的value(用来记录出现的频次)加 1;
如果不存在,则把它添加到 d 中,对应的 value 赋值为 1。最后,打印处 key_str 对应的 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));
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值