引子
为什么有哈希表?因为在很多场景下,对这种数据结构是由应用需求的,比如在编译阶段,需要形成对变量及其属性的有效管理,这样才能判断一些操作是否合法(比如数据是否重复定义,数据类型是否合法等),再比如一些应用设计字符串的比较,相比于数字比较,这种成本是增加的,那么是否可以把字符串转化为数字,再做处理呢?
这些问题都可以在学习散列表的过程中逐步解答。
正文
铺垫
先复盘一下已知的查找方法:
这里给出一个非常有趣的实际例子,登录qq时,服务器是如何快速在庞大的用户群中找到你的信息并快速核对的呢?
如果用二分法这种静态查找的方法的话,可以得出,
所以适应这种场景下的数据结构无疑等待着一个高效且动态的(随时可以添加/删除记录的)数据结构,再引出之前,我们先探讨一下查找的本质,那就是根据对象找到他的位置,这样就有两种思路:
- 有序安排对象,可以是全序(数组等)或者半序(查找树)
- 直接”算出“对象的位置,引出散列
基本介绍
散列表基本工作:
但是复杂度与函数计算和冲突有关。
哈希冲突
这里再引出一个哈希冲突的概念
有冲突怎么办呢?后来冲突的元素就没有位置了,直观想法是用二维数组,增加一个位置存放元素。但是这种缓解是有限的,如何有效解决呢?这就需要设计一个”好“的散列函数。一般满足下列条件:
哈希函数
-
直接定址法
-
除留余数法
-
数字分析法
比如身份证后几位比较随机
- 折叠法/平方取中法
如果关键词是字符又该如何处理呢?
- ASC码加和法
但是范围太窄,很多哈希冲突,可以改进:
以下是一段很巧妙的快速计算代码,可以研究下:
冲突处理方法
通常遇到冲突,常见思路有两种:
- 换个位置再存(开放地址法)
1. 线性探测
缺点是很容易连续冲突,不断试探位置,最后在冲突的地方越积累越多,最后造成冲突聚集现象。可以通过下图理解。
散列表的性能分析可以通过成功平均查找长度(即是比较次数且最后找到了)ASLs和不成功平均查找长度ASLu(即是比较次数且最后没找到)评价。
1. 平方探测
直观理解也能预期分布更均匀,观察下表一系列值存入哈希表的过程:
它的缺点是,可能空间不会充分利用,有些空间永远探测不到。但是他的优点是缓解了冲突聚集问题。
双散列探测法
其中di为探测偏移量。
- 同一位置的冲突对象可以组织在同一个单链表中(链地址法)
性能分析
应用
把握住一个重点,适合哈希查找的问题都是需要高效查找的并且多是动态的。
1. 词频统计(利用哈希查找快速查找和插入)
实现代码如下:
2. 找出”电话狂人“(出现频率最高的手机号)