目录
散列表
散列表:顾名思义也就是离散的或者零散,即不连贯的列表,也可以类比于离散数组。
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
散列表就是一个即查即得得一个表,python中的字典就是散列表,你输入键,他会很快返回一个值,时间复杂度基本为O(1),相比较列表和链表,运行速度快许多。
散列函数
就是一种映射关系的函数,就是无论你给它什么数据,它都还你一个数字,就是散列函数“将输入映射到数字”。
它也需要满足一些要求:
- 它必须是一致的。就是你输入同一个key时,f(key)得到的值都必须相同。
- 它应将不同的输入映射到不同的数字。就是尽量不同的key,对应着不同的f(key)值,这样才是一个好的散列函数。
散列函数总是将同样的输入映射到相同的索引,不同的输入映射到不同的索引,同时它知道数组长度,只返回有效的索引。这样散列函数每次都能准确找到输入对应的值。
我们就可以通过散列函数和数组构建出散列表这种数据结构,但是这不需要我们去实现,现在每一门语言都已经提供了散列表的实现,比如python中的字典。
良好的散列函数让数组中的值呈均匀分布。
糟糕的散列函数让值扎堆,导致大量冲突。
散列表的内部机制
冲突
就是给两个键分配的位置相同,即两个键映射到了同一个位置。
这时候的解决方法可以在这个位置存储一个链表(但建议不这样做),链表短的话,对整体性能没啥影响,但是链表一长,此时的散列表就会变得很慢,最好还是将这些键均匀的分布在散列表中,避免分配到同一个位置。
性能
在平均情况下(最优情况),散列表执行各种操作的时间都为O(1)。O(1)为常量时间,不代表马上,而是说无论散列表多大,运行的时间都相同。
平均情况 | 最糟情况 | 数组 | 链表 | |
查找 | O(1) | O(n) | O(1) | O(n) |
插入 | O(1) | O(n) | O(n) | O(1) |
删除 | O(1) | O(n) | O(n) | O(1) |
平均情况下,散列表运行的挺快,但是在出现前面所说的冲突时,它就会运行很慢,就很有可能达到这种最糟的情况。
因此我们需要避免冲突,即
- 较低的填装因子
- 良好的散列函数
填装因子
如
这个散列表的填装因子为3/5,但是当填装因子大于1意味着此时散列表包含的元素超过了数组的位置数,此时你就需要在散列表中添加位置,这样被称为调整长度(resizing)。
一般而言,当填装因子大于0.7时,就调整散列表的长度,通常是将数组长度扩大一倍。
散列表的应用案例
- 将散列表用于查询
- 防止重复,比如投票一人一票制
- 将散列表用作缓存,如web服务器,为了避免通过再次处理生成它们
小结
- 散列表是一种功能很强大的数据结构,操作速度快
- 我们不用自己去实现散列表,几乎所有的编程语言都提供了它的实现
- 你可以结合散列函数和数组来创建散列表
- 冲突很糟糕,尽量避免冲突
- 散列表的查找,插入和删除速度都非常快
- 散列表适合用于模拟映射关系
- 一旦填装因子超过0.7,就该调整散列表的长度
- 散列表可用于缓存数据(如在web服务器上)
- 散列表非常适合用于防止重复
注:内容来源于《算法图解》和百度百科