文章目录
- 散列表概念:
- 冲突:
- 填装因子:
- 性能:
- python示例代码:
- 总结:
散列表概念:
散列表 = 散列函数+数组(有时还要结合链表)实现的一种数据结构。
散列函数:将输入映射到数字。并且输入相同,映射的数字相同。输入不同映射的数字不同。
散列表的存储原理:对输入A生成唯一的hash,该hash对应到数组中的一个索引,在该索引上存放数据data,底层采用数组存储,意味着,获取数据时,只需要输入A,经过散列函数就可以获取对应的索引,从而立刻返回data.
一些别名:映射,程序中的实现(HashTable)
应用场景:
- 缓存,通过将url存入散列表。
- 防止重复,比如投票时,将人名和投票记录保存在散列表。
- 模拟映射关系:比如商品名称和价格的映射
冲突:
理想情况是散列函数总是将不同的键映射到数组的不同位置。但几乎不可能编写出这样的散列函数。比如通过商品的名称首字母分配数组的位置。比如将a开头的放在第一个位置,将b开头的放在第二个位置…,你会很快发现,有商品的首字母就会冲突。
解决冲突的最简单方法就是在冲突的位置存储一个链表。如下:
如果链表不是很长,我们还是会很快查到要查的商品。
填装因子:
填装因子=散列表元素个数 / 位置总数
比如:散列数组有5个位置,其中只有两个位置有元素,则占用率即填装因子是 2/5
填装因子大于1,意味着数组位置不够用,需要调整长度(resizing).一般是将数组增长为一倍。一般的经验,一旦填装因子大于0.7,就调整散列表的长度。
小结:
1.填装因子越低,发生冲突的可能性就越小,散列表的性能越高。
2.调整散列表需要很大开销,所以应该尽量避免。
3.良好的散列函数,能让数组中的值均匀分布。
性能:
散列表的时间复杂度:
操作 | 平均情况 | 最糟情况 |
查找 | O(1) | O(n) |
插入 | O(1) | O(n) |
删除 | O(1) | O(n) |
O(1)被称为常量时间,它并不意味着马上,而是不管散列表多大,所需的时间都相同。所以在平均情况下,散列表和数组一样快,而插入和删除速度和链表一样快。因此兼具两者的优点。
下面是数组和链表的时间复杂度:
数组 | 链表 | |
读取 | O(1) | O(n) |
插入 | O(n) | O(1) |
删除 | O(n) | O(1) |
小结:
所以如果考虑设计散列函数,查询单次时间等因素,要考虑进数据的量级,只有数据的量很大,散列函数很合理的情况下,散列函数的性能才能发挥到极致。
python示例代码:
几乎所有的编程语言都提供了散列表的实现。比如python创建一个散列表如下:
book = dict()
book["apple"] = 0.64
book["milk"] = 1.49
book["avocado"] = 1.43
#打印整个散列表
print(book)
#查询apple的价格
print(book["apple"])
程序屏蔽了散列函数的具体实现,我们只需要传入键和值,就会通过散列表保存,查询时,传入键就可以获取对应的值。
总结:
0.散列函数和数组结合可以创建散列表。
1.散列函数很重要,填装因子是散列函数性能的一个重要指标。
2.如果散列表存储的链表很长,速度将很慢,时间复杂度会从O(1)退化到O(n)。