目录
1)示例1:
2)散列函数
3)应用案例
4)冲突
5)性能
6)小结
本章内容:
学习散列表,最有用的数据结构之一。
学习散列表的内部机制:实现、冲突和散列函数。
1)示例1:
假设你在一家杂货店上班,有顾客来买东西时,你得在本子中查找价格。第一章介绍的简单查找,需要O(n)时间,如果你使用的是二分查找,时间为O(log n)。
二分查找的速度已经很快了,但作为收银员,在本子中查找价格是件很痛苦的事,即使本子的内容是有序的。在查找价格时,你都能感觉到顾客的怒气。但如果我们有一位雇员(Maggie),她能记住所有商品的价格,问她就能马上知道答案。这位雇员报出任何商品的价格的时间为O(1)。
2)散列函数
散列函数是这样的函数,无论你给它什么样的数据,它都还给你一个数字。
你可能认为散列函数输出的数字没什么规律,但其实散列函数必须满足一些要求。
- 它必须是一致的,输出与输入保持一致;
- 它应将不同的输入映射到不同的数字;
现在,我们可以打造我们的"Maggie"了。
首先创建一个空数组:
现在,我们将苹果的价格加入到数组中,输入为apple时,散列函数的输入为3,因此我们将苹果的价格存储到数组的索引3处。
不断地重复这个过程,最终整个数组将填满价格。
现在假设需要知道鳄梨(avocado)的价格,你无需在数组中查找,只需将avocado作为输入交给散列函数。,它会告诉你价格存储在索引4处。
散列函数之所以能准确地指出价格的存储位置,具体原因如下:
- 散列函数总是将同样的输入映射到相同的索引。
- 散列函数将不同的输入映射到不同的索引。
- 散列函数知道数组有多大,只返回有效的索引。
现在,我们可以结合散列函数和数组创建一个被称为散列表(hash table)的数据结构,在学习的复杂数据结构中,散列表可能是最有用的。Python提供的散列表实现为字典。
3)应用案例
散列表用途广泛,具体有:
- 手机里的电话簿,DNS解析;
- 防止重复;
- 将散列表用作缓存,缓存是一种常用的加速方式,所有的大型网站都使用缓存,缓存的数据存储在散列表中;
4)冲突
要明白散列表的性能,我们先搞清什么是冲突,现在我们有一个数组,它包含26个位置。
使用的散列函数非常简单,它按字母表顺序分配数组的位置。
现在,我们分别将苹果,香蕉、鳄梨的价格存储到散列表中,会出现下面这种情况:
这时出现了冲突(collision):给两个键分配的位置相同。
最简单的办法如下:如果两个键映射到同一个位置,就在这个位置存储一个链表。
通过上面的介绍,我们了解到散列函数很重要,理想的是散列函数将键均匀地映射到散列表的不同位置。
5)性能
散列表查找、插入、删除的运行时间如图所示:
在使用散列表是,避免最糟情况至关重要,为此,需要避免冲突,需要有:较低的填装因子,良好的散列函数。
填装因子度量的是散列表有多少位置是空的。一个经验是:一旦填装因子大于0.7,就调整散列表的长度。而什么是良好的散列函数,这不需要我们操心——天塌下来有高个子顶着。
6)小结
冲突很糟糕,你应使用可以最大限度减少冲突的散列函数。
散列表的查找、插入和删除速度都非常快。
散列表适合用于模拟映射关系。
一旦填装因子超过0.7,就该调整散列表的长度。
散列表可用于缓存数据(例如,在Web服务器上)。
散列表非常适合用于防止重复。