散列表
散列函数
定义
散列函数“将输入映射到数字”。即无论你给它什么数据,它都还你一个数字。
散列函数必须满足一些要求
l 它必须是一致的。例如,假设你输入apple时得到的是4,那么每次输入apple时,得到的都必须为4。
l 它应将不同的输入映射到不同的数字。 例如, 如果一个散列函数不管输入是什么都返回1,它就不是好的散列函数。最理想的情况是,将不同的输入映射到不同的数字。
散列函数准确地指出了存储位置,具体原因如下:
l 散列函数总是将同样的输入映射到相同的索引。
l 散列函数将不同的输入映射到不同的索引。
l 散列函数知道数组有多大,只返回有效的索引。
散列表
l 使用散列函数和数组创建了一种被称为散列表(hashtable)的数据结构。
l 散列表是一种包含额外逻辑的数据结构。数组和链表都被直接映射到内存,但散列表更复杂,它使用散列函数来确定元素的存储位置。
l 散列表也使用数组来存储数据,因此其获取元素的速度与数组一样快
python使用字典来实现散列表功能,可使用函数dict创建散列表
应用
将散列表用于查找
示例:
l 创建映射。
l 查找。
>>> phonebook={}
>>> phonebook['li']=123456
>>> phonebook['p']=987654
>>> print(phonebook['p'])
987654
防止重复
示例:检查是否存在某个元素
voted={}
def check_voter(name):
if voted.get(name): #使用函数get来返回是否存在。不存在返回None
print('kick them out!')
else:
voted[name]=True
print('let them vote')
check_voter('tom')
check_voter('jerry')
check_voter('jerry')
将散列表用作缓存
缓存的工作原理:网站将数据记住,而不再重新计算。
缓存优点
l 用户能够更快地看到网页
l 需要做的工作更少。
缓存是一种常用的加速方式,所有大型网站都使用缓存,而缓存的数据则存储在散列表中!
访问过程
cache={}
def get_page(url):
if cache.get(url): #检查缓存中是否存储了该页面
return cache[url] #存储了,即返回它
else:
data=get_data_from_server(url) #没存储,从服务器调用
cache[url]=data #将其存储到缓存中
return data #返回该页面
小结
散列表适合用于:
l 模拟映射关系;
l 防止重复;
l 缓存/记住数据,以免服务器再通过处理来生成它们
冲突(collision)
定义
给两个键分配的位置相同。
处理冲突的方式
如果两个键映射到了同一个位置,就在这个位置存储一个链表。
经验
l 散列函数很重要。避免散列函数将所有的键都映射到一个位置,而最理想的情况是,散列函数将键均匀地映射到散列表的不同位置。
l 如果散列表存储的链表很长,散列表的速度将急剧下降。然而, 如果使用的散列函数很好,这些链表就不会很长!
散列函数很重要,好的散列函数很少导致冲突。
性能
在平均情况下,散列表执行各种操作的时间都为O(1)。 O(1)被称为常量时间。
散列表的性能
操作 | 平均情况 | 最糟情况 |
查找 | O(1) | O(n) |
插入 | O(1) | O(n) |
删除 | O(1) | O(n) |
散列表同数组和链表比较
操作 | 平均情况 | 最糟情况 | 数组 | 链表 |
查找 | O(1) | O(n) | O(1) | O(n) |
插入 | O(1) | O(n) | O(n) | O(1) |
删除 | O(1) | O(n) | O(n) | O(1) |
在平均情况下,散列表的查找(获取给定索引处的值)速度与数组一样快,而插入和删除速度与链表一样快,因此它兼具两者的优点!
但在最糟情况下,散列表的各种操作的速度都很慢。
在使用散列表时,避开最糟情况至关重要。需要有:
l 较低的填装因子
l 良好的散列函数。
填装因子
计算公式
散列表的填装因子=散列表包含的元素数/位置总数
调整长度(resizing)
填装因子大于1意味着元素数量超过了数组的位置数。
一旦填装因子开始增大,就需要在散列表中添加位置,这被称为调整长度(resizing)
经验
填装因子越低,发生冲突的可能性越小,散列表的性能越高。
一个不错的经验规则是:一旦填装因子大于0.7,就调整散列表的长度。
良好的散列函数
良好的散列函数让数组中的值呈均匀分布。
糟糕的散列函数让值扎堆,导致大量的冲突。
小结
l 可以结合散列函数和数组来创建散列表。
l 冲突很糟糕,应使用可以最大限度减少冲突的散列函数。
l 散列表的查找、插入和删除速度都非常快。
l 散列表适合用于模拟映射关系。
l 一旦填装因子超过0.7,就该调整散列表的长度。
l 散列表可用于缓存数据(例如,在Web服务器上)。
l 散列表非常适合用于防止重复。