国庆假期在看<算法图解>这本书,感觉很有意思.
结合学到的知识,以及我的理解,来聊聊散列表这部分的内容.
假设你现在在一家超市工作,有顾客来买东西时,需要在一个本子上查找价格.如果记录价格的本子不是按照字母顺序来排列的,为了找一个商品的价格,可能 1 分钟的时间就过去了.如果让顾客等这么长时间,确定这家超市还能存活下去?
为了提高准确找到商品的价格,咱们可以对这个方法进行一个优化.第一想到的应该就是让本子的记录内容由顺序,比如从 A 到 Z 来开始排列,这样如果顾客拿着「可乐」来结账的时候,只需要定位到 k 开头的位置就行了,然后再寻找.
这样的方式是比刚开始好很多,但是你要知道,顾客就是上帝啊!顾客才不会等这么长时间.顾客期望的结果是什么,你看到这个商品,立刻就能知道是多少钱,结完账立刻让顾客走.
这个时候怎么办?是不是觉得如果有个机器人,能够装得下所有商品的价格就好了?这样顾客来了,问一下机器人就可以知道了.
在计算机中,有一种数据结构就可以满足这个场景,叫散列表,说它的另外一个名字可能就不生疏了,哈希表( Hash Table ),它由健和值组成,最大的特点就是,不管你要访问什么,它的反应时间都是 O(1).
数组和链表都是直接被映射到内存,但是散列表不一样,它使用散列函数来确定元素的存储位置.
散列函数如果想要符合上面的应用场景,必须满足一些要求:
- 数据必须是一致的.比如,我第一次输入苹果的时候,得到的价格是 4 元/斤,那我下次输入苹果的时候,也必须是 4 元/斤才行.
- 对于不同的输入,应该有不同的输出.如果不管我输入什么值,你都返回给我同样的值,这就不能认为是一个好的散列函数.
- 散列函数知道数组有多大,只返回有效的索引,假设数组包含 5 个元素,结果散列函数返回无效索引 100 ,就不能满足上面的应用场景.
基于散列函数和数组,在以下应用场景中可以考虑使用散列表:
1 ,用于查找.
前一段时间一直分享网络方面的知识,估计看到这里也会比较熟悉, DNS 解析的时候,就是将一个网站映射到了 IP 地址,这样用户才能访问到一些资源.而散列表就是提供这种功能的方式之一.
2 ,防止重复.
现在需要大家来投票进行选择新人奖,很显然,不能刷票吧,一个人只能投一票.但是如何避免重复投票呢?是不是有人来投票的时候,将他的名字和已经投过票的名单进行对比就行了,如果有,那就不让投了,如果没有,可以投票.
这个时候,创建一个散列表,用来记录已经投过票的人,接下来有人投票时,检查是否在散列表中,如果在,抱歉不能投了,如果不在,可以投票.
3 ,可以用做缓存.
缓存这个应该是比较熟的,网站为了提高用户的体验度,都会做缓存.那么这些缓存是如何存储的呢?
没错,缓存的数据存储在了散列表中!
因为它的运行时间为 O(1) ,用户来访问的时候,可以通过这种方式,快速给用户以反馈.
但是散列函数也是有自己的不足的.
在上面,我们忽略了一点,就是空间是有限的.
如果只有 5 个空间,但是通过散列函数输入了 6 个值,肯定会有一个冲突,怎么办?
方法有很多,比如线性探测法,二次探测法,随机弹测法等等,最简单的就是如果两个键映射到了同一个位置,那就在这个位置再存储一个链表.
如果数据量很大,就有一种可能,这个位置的链表会很长很长,这个时候,散列表的速度就会很慢.
这个时候,就能看到一个好的散列函数的重要性了.
如果有一个好的散列函数,那就不会出现上面的问题.
什么是好的散列函数呢?一般是拿填装因子来确定的.填装因子很好计算,就是散列表中有多少,总数有多少,两者一除就是填装因子.假设建有一个散列表,有 3 个位置,用了 1 个,那么这个散列表的填装因子就是 1/3 .
目前,大多数语言都提供散列表的实现,所以单单从使用上来说,不需要 care 如何实现.
以上,感谢您的阅读~