数据结构中的哈希表

前言

哈希表是一种非常重要的数据结构,几乎所有的编程语言都有直接或间接的使用到这种数据结构。

介绍

哈希表通常是基于数组进行实现的,但是相对于数组,哈希表有许多的优势

  1. 它可以提供非常快速的插入-删除-查找操作。
  2. 在哈希表种,无论多少数据,插入和删除值只需要接近常量的时间:即O(1)的时间级。在进行操作时只需要几个机器指令即可完成。其速度比树还要快,基本可以瞬间查找到想要的元素。

但是哈希表也有不足

  1. 哈希表种的数据是没有顺序的,所以不能以一种固定的方式(比如从小到大)来遍历其中的元素。
  2. 通常情况下,哈希表中的key是不允许重复的,不能放置相同的key,用于保存不同的元素。

那哈希表究竟是什么呢?

这是百度百科对哈希表的描述:散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

上面的解释可能有点难理解,哈希表的结构其实就是数组,但是它神奇的地方在于对下标值的一种变换,这种变换的方法称为哈希函数,通过哈希函数可以获取到HashCode。

案例

下面我们用一个案例来理解哈希表这种结构:

现在我们有50000个单词要进行存储,我们要使用什么数据结构对这些单词进行存储呢?

首先使用数组来存储:
50000个单词,就能看出数组的缺陷了。比如我想在数组拿到python这个单词的信息,在数组中只能进行线性查找,通过一个一个单词的比对来进行查找,这样效率就会非常的低,显然这种方式是不可行的。

其次我们使用链表结构来存储:
问题也跟数组一样需要通过线性查找,也是不可行的。

那么有没有一种方案可以将单词转成数组的下标呢?
如果能够将单词转成数组的小标,那么以后我们要查找某个单词信息的时候,就可以将单词转成对应的下标来快速访问到我们要查找单词的信息。而哈希表中就实现了将元素转成下标再进行存储这种方式

那哈希表的这种存储方式是如何实现的呢?

将元素通过幂的连乘获取到一个数值,再通过哈希化来对数值进行转化得到最终的值就是该元素的下标。

具体实现:

现在有一个单词 “cats”,首先我们通过幂的连乘:327^3 + 127 ^2 + 20*27 + 17 = 60337 (为了好理解这里的字母从a-z用1-26表示,正常应该是按照ASCii码表:a=97、b=98 、…) ,这样得到的数字就可以基本保证它的唯一性,不会和别的单词重复。

这个时候就出现问题了,如果一个单词是zzzzzzzzzz(一般英文单词不会超过10个字符),那么得到的数字超过7000000000000,创建这么大的数组,事实上会有很多数组下标指向的是空,所以创建这么大的数组是没有意义的。

这个时候就需要一种压缩方法,把上述幂的连乘方法中得到的巨大整数范围压缩到可接受的数组范围中。

以上面的单词存储为例,50000个单词,可能会定义一个长度为50000的数组。但实际情况中,往往需要更大的空间来存储这些单词,因为我们不能保证单词会映射到每一个位置。

哈希化:

那如何压缩呢?进行取余操作。

为了能够更好的理解这个方法是如何工作的,我们先来看一个小点的数字范围压缩到一个小点的空间中。
假设把从0-199的数字(使用largeNumber表示)压缩为到0-9的数字(使用smallRange表示)

下标(index) = largeNumber % smallRange

当一个数被10整除时,余数一定在0-9之间,比如13%10 = 3,157%10 = 7。

当然这中间还是会有重复,不过重复的数量明显变小了,因为我们的数组长度是100000,而只有50000个单词。相当于在0-199中间选取5个数字放在长度为10的数组中,也会有重复,但是重复的概率非常小。我们称这种重复为冲突。

冲突

上面说到的重复问题就称为冲突。其实就是经过哈希化后不同元素得到的下标相同的情况,如下图所示,下标为2的B元素已经在数组里了,但是新添加的元素经过哈希化后下标也为2。这时候该如何解决呢?下面就来说明遇到冲突的解决方法。
在这里插入图片描述

解决冲突的方法

解决冲突常见有两种方法:

  1. 链地址法
  2. 开放地址法

1、链地址法:

这种方法每一个数组单元中存储的就不是单个数据了而是一个链条。
当遇到下标重复的元素添加进来时,将新添加进来的元素插入到链条的首段或者末端中即可。
当需查找时,就先通过哈希化得到数组中的下标,然后再通过在该下标中的链条中线性查找出对应的元素即可。
在这里插入图片描述

2、开放地址法:

这种方法在遇到冲突的时候往后查找空的数组单元,找到空的后再进行插入
在这里插入图片描述但是这个时候又有问题了,如何选择插入的位置呢?

有三种方法:
  1. 线性探测
  2. 二次探测
  3. 再哈希法

2.1、线性查找:

当哈希化得到的下标(index)在数组中已经存在元素时,从下标为 index + 1 的位置开始逐个查找,直到找到空位置为止,再进行插入。

2.2、二次探测:

如果之前的数据是连续插入的话,那么使用上一种方法查找的距离可能就会很长。
于是二次探测这种方法就是对查找距离(步长)进行了优化,从下标X开始,下一个到x + 1^2 再到 x+2 ^2 再到x+3 ^2这样查找下去,直到找到空位置为止。

2.3、再哈希法:

再哈希法的做法就是把关键字用另外一个哈希函数再做一次哈希化,用这次的哈希化的结果作为步长,这样就可以实现每一个下标的查询都有不同的步长。

以上就是作者对哈希表的理解,如有不足或者错误之处欢迎各位指正。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值