文章目录

  • 散列表概念:
  • 冲突:
  • 填装因子:
  • 性能:
  • python示例代码:
  • 总结:


散列表概念:

散列表 = 散列函数+数组(有时还要结合链表)实现的一种数据结构。

散列函数:将输入映射到数字。并且输入相同,映射的数字相同。输入不同映射的数字不同。

散列表的存储原理:对输入A生成唯一的hash,该hash对应到数组中的一个索引,在该索引上存放数据data,底层采用数组存储,意味着,获取数据时,只需要输入A,经过散列函数就可以获取对应的索引,从而立刻返回data.

一些别名:映射,程序中的实现(HashTable)

应用场景:

  • 缓存,通过将url存入散列表。
  • 防止重复,比如投票时,将人名和投票记录保存在散列表。
  • 模拟映射关系:比如商品名称和价格的映射
冲突:

理想情况是散列函数总是将不同的键映射到数组的不同位置。但几乎不可能编写出这样的散列函数。比如通过商品的名称首字母分配数组的位置。比如将a开头的放在第一个位置,将b开头的放在第二个位置…,你会很快发现,有商品的首字母就会冲突。

解决冲突的最简单方法就是在冲突的位置存储一个链表。如下:

java 散列表实现 散列表定义_散列函数

如果链表不是很长,我们还是会很快查到要查的商品。

填装因子:

填装因子=散列表元素个数 / 位置总数

比如:散列数组有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)。

java 散列表实现 散列表定义_数据结构_02