散列表

散列表可能是最有用的,也被称为散列映射、映射、字典和关联数组。

性能

散列表包含一个元素还是10亿个元素,从其中获取数据所需的时间都相同。实际上,你以前见过常量时间——从数组中获取一个元素所需的时间就是固定的:不管数组多大,从中获取一个元素所需的时间都是相同的。在平均情况下,散列表的速度确实很快。
在最糟情况下,散列表所有操作的运行时间都为O(n)——线性时间,这真的很慢。散列表同数组和链表比较一下。
在这里插入图片描述
在平均情况下,散列表的查找(获取给定索引处的值)速度与数组一样快,而插入和删除速度与链表一样快,因此它兼具两者的优点!但在最糟情况下,散列表的各种操作的速度都很慢。因此,在使用散列表时,避开最糟情况至关重要。为此,需要避免冲突。而要避免冲突,需要有:

  1. 较低的填装因子
  2. 良好的散列函数

冲突

散列函数总是将不同的键映射到数组的不同位置,这是不对的。实际上,几乎不可能编写出这样的散列函数。看一个简单的示例。假设有一个数组,它包含26个位置。而使用的散列函数非常简单,它按字母表顺序分配数组的位置。如果将苹果(Apple)的价格存储到散列表中,分配的是第一个位置。接下来,将香蕉(Bananas)的价格存储到散列表中,分配的是第二个位置。一切顺利!但现在要将鳄梨(Avocados)的价格存储到散列表中,分配的又是第一个位置。这种情况被称为冲突(collision):给两个键分配的位置相同。这是个问题。如果将鳄梨的价格存储到这个位置,将覆盖苹果的价格,以后再查询苹果的价格时,得到的将是鳄梨的价格!冲突很糟糕,必须要避免。处理冲突的方式很多,最简单的办法如下:如果两个键映射到了同一个位置,就在这个位置存储一个链表。
在这个例子中,apple和avocado映射到了同一个位置,因此在这个位置存储一个链表。在需要查询香蕉的价格时,速度依然很快。但在需要查询苹果的价格时,速度要慢些:必须在相应的链表中找到apple。如果这个链表很短,也没什么大不了——只需搜索三四个元素。但是,假设杂货店只销售名称以字母A打头的商品。
在这里插入图片描述
除第一个位置外,整个散列表都是空的,而第一个位置包含一个很长的列表!换言之,这个散列表中的所有元素都在这个链表中,这与一开始就将所有元素存储到一个链表中一样糟糕:散列表的速度会很慢。

教训
  1. 散列函数很重要。前面的散列函数将所有的键都映射到一个位置,而最理想的情况是,散列函数将键均匀地映射到散列表的不同位置。
  2. 如果散列表存储的链表很长,散列表的速度将急剧下降。然而,如果使用的散列函数很好,这些链表就不会很长!

总结

  1. 可以结合散列函数和数组来创建散列表。
  2. 冲突很糟糕,应使用可以最大限度减少冲突的散列函数。
  3. 散列表的查找、插入和删除速度都非常快。
  4. 散列表适合用于模拟映射关系。
  5. 一旦填装因子超过0.7,就该调整散列表的长度。(填装因子= 数列表包含的元素数/位置总数)
  6. 散列表可用于缓存数据(例如,在Web服务器上)。
  7. 散列表非常适合用于防止重复。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值