本章内容:
- 学习散列表——最有用的基本数据结构之一,用途广泛
- 学习散列表的内部机制:实现、冲突、散列函数。理解如何分析散列表的性能
在学习的复杂数据结构中,散列表可能是最有用的,也被称为散列函数、映射、字典(python提供的散列表实现为字典)和关联数组
目录
散列函数
散列表是学习的第一种包含额外逻辑的数据结构。数组和链表都直接被映射到内存,但散列表更加复杂,它使用散列函数来确定元素的存储位置。散列表的速度很快!
任意一门优秀的语言都提供了散列表的实现。python提供的散列表实现为字典,你可以用函数dict来创建散列表。
book['apple'] = 0.67
book['banana'] = 0.3
book['milk'] = 1.49
print(book)
print(book['apple'])
#输出结果:
{'apple': 0.67, 'banana': 0.3, 'milk': 1.49}
0.67
散列表由键和值组成
应用案例
将散列表用于查找
散列表可以轻松的模拟映射关系
比如你在访问一个网站的时候,无论是什么网站,计算机必须将网址转化为ip地址,这个过程称为DNS解析(DNS resolution),散列表就是提供这种功能的方式之一。
比如上面的那个代码片段就是用来查找。
防止重复
比如你负责一个投票站。显然每个人都只能投一票,但是如何来避免重复投票呢,有个好方法,就是使用散列表。
#创建一个散列表,用于记录已经投过票的人
voted = {}
def check_votor(name):
if voted.get(name):
#if Ture,那么就执行
print('has voted!')
else:
voted[name] = True
#把这个新的名字加进去
print('let ta vote!')
check_votor('Tom')
check_votor('Mike')
print(voted)
check_votor('Mike')
#输出
let ta vote!
let ta vote!
{'Tom': True, 'Mike': True}
has voted!
使用散列表来检查是否重复,速度非常快!
将散列表用作缓存
缓存的原理:网站将数据记住,而不是重新计算
ta不让服务器去生成网页,而是将网页存储起来,并在需要的时候将其直接发送给用户。缓存是一种加速方式,所有大型网站都使用缓存,二缓存的数据则存储在散列表当中!
cache = {}
#缓存的散列表
def get_page(url):
if cache.get(url):
#从缓存里面找,要是找的到的话就是直接输出该网页存储的信息
return cache[url]
else:
data = get_data_from_server(url)
#找不打的话就从服务器里面去找
cache[url] = data
#同时将这个网页的数据存储下来
return data
#返回这个网页的数据
小结一下散列表的用途:
- 模拟映射关系
- 防止重复
- 缓存数据,以免服务器再处理生成ta们
冲突和性能
虽然散列表查找的速度很快,但是依然要明白散列表的性能,在了解这个之前,需要明白什么是冲突。
冲突
冲突(collision):给两个键分配的位置相同
什么意思呢,就是python的字典里面一般不都是键值对的嘛,这个时候一个键会对应多个值
处理冲突的办法很多,最简单的方法就是:如果两个键映射到一个位置,那么就在这个位置存储一个链表。
其实吧,在里面再放一个散列表也没的关系。
book = dict()
book['a'] = [("apple",0.67),('avocado',0.54)]
book['banana'] = 0.3
book['milk'] = 1.49
print(book)
print(book['a'][0][1])
#输出
{'a': [('apple', 0.67), ('avocado', 0.54)], 'banana': 0.3, 'milk': 1.49}
0.67
但是这个有缺点,加入数组后,数据的查找速度会慢下来。因此最理想的情况是,散列表将键均匀地映射到散列表的不同位置
性能
散列表/数组/链表 大O运行时间 | ||||
operation | 散列表 (平均情况) | 散列表 (最糟情况) | 数组 | 链表 |
查找 | O(1) | O(n) | O(1) | O(n) |
插入 | O(1) | O(n) | O(n) | O(1) |
删除 | O(1) | O(n) | O(n) | O(1) |
为了避免冲突,需要有:
- 较低的填充因子(填充因子=列表包含的元素数/位置总数,一旦超过0.7那就应该调整散列表的长度)
- 良好的散列函数(不用担心)