算法图解之散列表
1. 散列函数
散列函数就是将输入映射到数字。它必须满足两个条件:
- 输出必须一致,每次输出同一个key,都应得到同样的value。
- 将不同的输入映射到不同的数字,也就是不能输入什么key都得到同样的value。
如下图所示:
散列函数可以准确的指出价格的存储位置,具体原因如下:
- 散列函数总是将同样的输入映射到相同的索引。也就是说apple永远在第四个格子上
- 散列函数将不同的输入映射到不同的索引。apple的索引为3,milk的索引为0
- 散列函数知道数组有多大,只返回有效的索引
2. 应用案例
1. 手机内置的电话簿
2. DNS解析,网址对应IP地址
3. 防止重复的一些场景,比如投票、领奖
4. 缓存。比如说Facebook,它会把经常访问但又不需要动态传递数据的网页的数据储存在散列表中,比如about、注册、登陆页面。当你访问Facebook的页面时,它首先检查散列表中是否存储了这些页面。这样可以大大降低服务器的压力,而且好的散列的运行时间是O(1),效率比去服务器拿数据要快得多。
3. 冲突
冲突就是给两个或多个键分配的位置相同。如果我们按字母表,给散列函数26个位置,那么相同开头的字母就会占据同一个位置,如下图:
这会比一开始将所有元素储存到一个链表中还慢。
总结:
1. 要想办法把散列函数的键均匀地映射到散列表的不同位置。
2.如果散列表存储的链表很长,散列表的速度将急剧下降。然而,如果使用的散列函数很 好,这些链表就不会很长!
3. 散列函数很重要,好的散列函数很少导致冲突。
4. 性能
在平均情况下,散列表的查找速度与数组一样快,而插入和删除速度与链表一样快,因此它兼具两者的优点。但在最糟情况下,散列表的各种操作的速度都很慢。
避免最糟糕情况的关键是避免冲突,这需要:
- 较低的填装因子
- 良好的散列函数
4.1 填装因子
填装因子 = 散列表包含的元素书 / 位置总数
一个经验规则是填装因子一旦大于0.7,就需要调整散列表的长度(resizing),通常将数组增长一倍,然后使用函数hash将所有的元素都插入到新的散列表中。
4.2 良好的散列函数
良好的散列函数让数组中的值呈均匀分布
糟糕的散列函数让值扎推,导致大量的冲突
5.总结
一般的编程语言都实现了散列列表,而且能够获得平均情况下的性能(包括Python):常量时间,O(1)
散列表是一种功能强大的数据结构,其操作速度快,还能让你以不同的方式建立数据模型。你可能很快会发现自己经常在使用它。
- 你可以结合散列函数和数组来创建散列表。
- 冲突很糟糕,你应使用可以最大限度减少冲突的散列函数。
- 散列表的查找、插入和删除速度都非常快。
- 散列表适合用于模拟映射关系。
- 一旦填装因子超过0.7,就该调整散列表的长度。
- 散列表可用于缓存数据(例如,在Web服务器上)。
- 散列表非常适合用于防止重复。