章节内容
- 学习散列表
- 学习散列表的内部机制:实现、冲突和散列函数
散列函数
散列函数将输入映射到数字。散列函数必须满足以下要求:
- 多次的散列值必须一致。每次输入相同的值时,映射到的数字必须一致
- 必将不同的输入映射到不同的数字
在python中,提供了字典作为散列表的实现:
book = dict()
book["apple"] = 0.67
book["milk"] = 1.49
book["avovado"] = 1.49
print book["apple"] //输入0.67
应用案例
将散列表用于查找
- 上面python代码中散列表用于查找商品的价格。
- 手机中的电话簿也适合用散列表存储。
- DNS服务器上存储网址和IP的映射关系也适合用散列表,这个查找的过程叫做DNS解析。
防止重复
一个投票站,每人只能投一票。我们可以用散列表存储每个人以及Ta的投票。
当有人来投票,我们可以先从散列表获取这个人的投票,如果返回None,则可以投票。
在一些中间件(比如Redis)、编程语言中,也用散列表实现集合Set。
将散列表用作缓存
缓存是一种常用的加速方式,而缓存的数据则存储在散列表中。
memcached、分布式缓存中间件Redis、google开源的leveldb,都是用散列表实现的存储。
所有大型网站都使用了缓存,比如Facebook缓存了用户的页面,加速用户加载自己的主页等页面
代码实现如下:
cache = {}
def get_page(url):
if cache.get(url):
return cache[url]
else:
data = get_data_from_server(url)
cache[url] = data
return data
冲突
其实上文中说到的散列表将不同的输入映射到不同的数字这个是几乎不可能的。
我们只能尽量做到让散列函数尽可能产生不一样的数字,如果产生了一样的数字就意味着发生了冲突。
发生冲突最经常使用的是拉链法,即在散列数组的位置上放一个链表,将冲突的输入都放入链表中。
散列函数不好的情况下,可能输入的元素大部分在散列数组某个位置的链表中,这样和用链表存储的效率一样糟糕。
因此,散列函数对于散列表的性能起到关键作用。
性能
散列表在最糟的情况下,查找的运行时间为O(n)--线性时间;在平均情况下,查找运行时间为O(1)。
为了避开最糟情况,需要避免冲突。而要避免冲突,需要有:
- 较低的填装因子
- 良好的散列函数
填装因子
散列表的填装因子=散列表保函的元素数/位置总数
我们通常给散列表加上填装因子的阈值,当散列表的填装因子大于阈值,则会进行散列数组扩容。
良好的散列函数
良好的散列函数让数组中的值呈均匀分布。
通用我们用SHA函数用作散列函数。
小结
- 你可以结合散列函数的数组创建散列表
- 冲突很糟糕,你应使用可以最大限度减少冲突的散列函数
- 散列表的查找、插入和删除速度都非常快
- 散列表适合用于模拟映射关系
- 一旦填装因子超过0.7,就该调整散列表的长度
- 散列表可用于缓存数据
- 散列表非常适合用于防止重复