【从0到1学算法】散列表

命名难 (NameFail)
• 阅读 713

这可能是这么多种数据结构中最有用的-----散列表。

一、什么是散列表

超市中用到的条形码,每个码对应一个商品,扫一下马上就能知道商品的价格,查询速度O(1)。哪种数据结构能做到这样?那只有散列表了。

散列函数

首先需要理解散列函数,散列函数是散列表的灵魂。

散列函数是这样的函数,无论你给他什么数据,它都还给你一个数字。

【从0到1学算法】散列表

专业点说,就是散列函数“将输入映射到数字”。

散列函数映射数字有这些规则:

1.相同的输入,输出必定也相同。例如,假设输入apple得到4,那每次输入apple得到都是4。

2.不同的输入映射到不同数字。(这是最理想情况)

这有何用途?当然是用来打造散列表。

首先创建一个空数组。

【从0到1学算法】散列表

我们将在这个数组中存储商品价格。下面将苹果的价格加入这个数组中,输入apple到散列函数。输出为3,因此将苹果价格存储的索引3位置。

【从0到1学算法】散列表

【从0到1学算法】散列表

下面将牛奶价格存储到数组中。

【从0到1学算法】散列表

【从0到1学算法】散列表

不断重复这个过程,最终将数组填满。

【从0到1学算法】散列表

现在你想知道鳄梨(avocado)的价格,你无需遍历数组查找,只需将avocado作为输入交给散列函数,它就会帮你找到它。

【从0到1学算法】散列表

【从0到1学算法】散列表

这便是散列表,利用散列函数构造的数据结构,能够快速找到想要的数据,理想情况下速度为O(1)。散列表可能是你学习的复杂数据结构中最有用的,也成为散列映射、映射、字典和关联数组。

很多时候你根本不需要自己去实现散列表,在很多优秀语言中都提供了散列表的实现。比如Java中的Map, Python中的字典Dictionary。

二.冲突

前面我们说到,散列函数在理想情况下,不同的输入映射到不同数字。但没有那么多的理想情况,有时候散列函数会发生冲突,这影响着散列表的性能。

假设有这样一个数组,它包含26个位置。

【从0到1学算法】散列表

而使用的散函数很简单:按字母表顺序分配数组的位置。

【从0到1学算法】散列表

将苹果价格存储到散列表中,分配的是第一个位置。香蕉则是第二个位置。

【从0到1学算法】散列表

【从0到1学算法】散列表

然而,如果要将鳄梨(avocado)存进去,分配的还是第一个位置,可是第一个位置已经放了苹果!这种情况被称为冲突:给两个键分配相同位置。

【从0到1学算法】散列表

处理冲突的方式有很多,其中最简单是拉链法:如果连个键映射到同一个位置,就在这个位置上存储一个链表。

【从0到1学算法】散列表

在这个例子中,查询香蕉的价格依然很快,而查询A开头的物品时就慢一些,因为需要遍历链表。如果这个链表很短,那没什么大不了,只需搜索几个元素即可。但是,假设这散列表中只存在以字母A开头的物品,这就很糟糕了!散列表会很慢。

【从0到1学算法】散列表

这里可得这样的经验教训。

  1. 散列函数很重要,最坏的情况是所有键都映射到同一个位置,最理想的情况是不同键映射到不同位置。
  2. 散列表的链表很长,查询速度会急剧下降。良好的散列函数,不会导致很长的链表。

良好的散列函数是避免冲突的关键之一。

三、填装因子

较低的填装因子是避免冲突的关键之二。填装因子计算公式为:散列表包含的元素数/位置总数。例如,下面的散列表的填装因子为2/5=0.4

【从0到1学算法】散列表

一旦填装因子大到一定程度,就需要在散列表中添加位置,这被称为调整长度。通常会将数组增长一倍。

例如下面这个散列表,规定达到3/4时调整长度。

【从0到1学算法】散列表

这是需要调整长度,首先创建一个更长的新数组:长度为原来的2倍。

【从0到1学算法】散列表

接下来,通过散列函数将所有元素插入到这个新数组中。【从0到1学算法】散列表

填装因子越低,发生冲突的可能性越小,散列表性能越高。但是填装因子越低,空间利用率就越低,所以需要取平衡。一个不错的经验规则是:一旦填装因子大于0.7,就调整散列表长度。

四、应用案例

1.快速查找

在大量的数据中查找想要的信息,散列表是一个不错的选择。

比如电话本,将每个姓名映射到电话号码

【从0到1学算法】散列表

【从0到1学算法】散列表

或是DNS解析。在你访问一个网址时,比如http://adit.io,在DNS服务器会将它转换为IP地址。

【从0到1学算法】散列表

无论访问哪个网址,它都必须转换为IP地址。

【从0到1学算法】散列表

网址映射到IP地址,这很适合用散列表。

2.防止重复

散列表中每个键只会对应一个位置,无法存储相同的键,这可以起到防重复的效果。

比如,现在需要创建一个投票程序,每个人只能投一票,我们可以用散列表来检查这个人是否已投过票。

【从0到1学算法】散列表

3.用作缓存

还有一个重要应用:缓存。其中网页缓存,我们应该经常听到。

假设你正在访问Facebook登录页面,这是一个通用的页面,经常会被缓存到你电脑中。当你第二次打开登录页面,你会发现会比第一次打开的速度快,因为你访问的是你电脑中的缓存数据,而从Facebook服务器下载数据。

除了登录页,一般还会存储主页、About页面、Contact页面等等。

而散列表是这样起到缓存作用的:

【从0到1学算法】散列表

小结

  • 散列表可以用散列函数和数组构成。
  • 冲突很糟糕,会严重影响散列表的性能。
  • 避免冲突的两个关键:
  1. 良好的散列函数
  2. 较低的填装因子
  • 常见应用
  1. 快速查找
  2. 防止重复
  3. 缓存

如果文章觉得不错,别忘了点赞、评论、转发哦!

文章首发于公众号【KEN DO EVERTHING】
本公众号专注于java相关技术,但不限于java、mysql、python、面试技巧、生活感悟等。分享优质博文,技术干货,学习资源等优质内容。
欢迎关注,一起学习,共成长!

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
java容器之HashMap
HashMap采用了数组和链表的数据结构,能在查询和修改方便继承了数组的线性查找和链表的寻址修改,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。解决哈希冲突的三个方法:a.开放定址法  又被称为再散列法,包括线性探测再散列、二次探测再散列、伪随机探测再散列b.再哈希法  地址冲突后,对哈希结果再次进行哈希,直到
哈希游戏搭建开发主要原理
区块链的算法主要有两个部分,一个是哈希算法,一个是非对称加密。哈希(Hash)是一种加密算法,也称为散列函数或杂凑函数。哈希函数是一个公开函数,可以将任意长度的消息M映射成为一个长度较短且长度固定的值H(M),称H(M)为哈希值、散列值(HashValue)、杂凑值或者消息摘要。它是一种单向密码体制,即一个从明文到密文的不可逆映射,只有加密过程,没有解密过
Stella981 Stella981
3年前
Python数据结构与算法——散列(Hash)
!(https://oscimg.oschina.net/oscnet/19a7428dd9c64d149aa474d3aabe80ce.png)点击上方“蓝字”关注我们散列(Hash)对于一组数据项,顺序查找的时间复杂度是O(n),二分查找是O(logn),而对于散列的数据结构,
Stella981 Stella981
3年前
Nginx数据结构之散列表
1\.散列表(即哈希表概念)散列表是根据元素的关键码值而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找速度。这个映射函数f叫做散列方法,存放记录的数组叫做散列表。若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上。由此,不需要比较便可直接取得所
Stella981 Stella981
3年前
Redis 为什么这么快? Redis 的有序集合 zset 的底层实现原理是什么? —— 跳跃表 skiplist
Redis有序集合zset的底层实现——跳跃表skiplistRedis简介Redis是一个开源的内存中的数据结构存储系统,它可以用作:数据库、缓存和消息中间件。它支持多种类型的数据结构,如字符串(Strings),散列(Hash),列表(List),集合(S
Stella981 Stella981
3年前
Redis为什么这么快
Redis简介Redis是一个开源的内存中的数据结构存储系统,它可以用作:数据库、缓存和消息中间件它支持多种类型的数据结构,如字符串(String),散列(Hash),列表(List),集合(Set),有序集合(SortedSet或者是ZSet)与范围查询,Bitmaps,Hyperloglogs和
Stella981 Stella981
3年前
Hash算法解决冲突的四种方法
Hash算法解决冲突的方法一般有以下几种常用的解决方法 1,开放定址法: 所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入 公式为:fi(key)(f(key)di)MODm(di1,2,3,……,m1) ※用开放定址法解决冲突的做法是:当冲突发
Stella981 Stella981
3年前
Redis 底层数据结构介绍
!Redis底层数据结构(https://oscimg.oschina.net/oscnet/2bdc88a69f3d195776f6b395ddc914775f8.png)Redis底层数据结构版本:2.9支持的数据类型:1.字符串2.散列3.列表4.集合5.有序集合字符串
Wesley13 Wesley13
3年前
1.8 可变、不可变数据与hash
HASH   Hash,一般翻译做'散列',也有直接音译为'哈希'的,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转化是一种压缩映射,也就是,散列值得空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一确定输入值。简单的说就是一种将任意长度的消息压
V-275670029 V-275670029
2年前
哈希竞猜游戏的原理
Hash一般被翻译成“散列”,也可直接音译为“哈希”,就是把任意长度的输入(又叫做预映射,preimage),通过散列算法,变换成固定长度的输出,该输出就是散列值。  这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消
搭建平台吧 搭建平台吧
2年前
哈希竞猜的未来趋势
哈希(Hash)是一种加密算法,也称为散列函数或杂凑函数。哈希函数是一个公开函数,可以将任意长度的消息M映射成为一个长度较短且长度固定的值H(M),称H(M)为哈希值、散列值(HashValue)、杂凑值或者消息摘要。它是一种单向密码体制,即一个从明文到密文的不可逆映射,只有加密过程,没有解密过程。一致性hash算法提出了在动态变化的Cache环境中,判定