【JS数据结构与算法】认识哈希表

目录

一、什么是哈希表?

二、哈希表的优势。

三、哈希表与数组相比较。

四、数据的存储。

方法一:ASCII编码之和

方法二:幂的连乘

五、方法二改进——哈希化

六、解决冲突

一:链地址法(拉链法)。

二:开放地址法。

1.线性探测法:线性的查找空白的地方进行存放。

2.二次探测法,第一次散列产生哈希地址冲突,经过另外一个散列函数(哈希函数)对冲突结果进行处理。

3.再哈希法。

4. 随机探测再散列。

三: 再散列法。

四: 建立一个公共溢出区


一、什么是哈希表?

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

二、哈希表的优势。

  1. 它可以提供非常快速的插入-删除-查找的操作。
  2. 不管数据的多少,插入和删除值所需要接近常量的时间,即O(1)的时间级,实际上只需要几个机器指令就可以完成了。
  3. 哈希表的速度比树还要快,可以瞬间查找到想要找的元素。
  4. 哈希表的原理可能复杂一些,但是代码写起来会容易很多。

三、哈希表与数组相比较。

  1. 哈希表中的数据是没有顺序的
  2. 通常情况下,哈希表中的key值是不允许重复的,不能放置相同的key值,因此不可以存放相同的元素。
  3. 对于数组来说。
  • 进行插入操作时,效率极其低。
  • 如果是查找操作的话,如果是基于索引进行查找的话效率就相当的高,如果没有的话(比如基于内容去查找)效率就很低。
  • 数组在删除操作时,效率也很低。

哈希表是基于数组的优化(对下标值进行变换)

eg:假如我现在想要在一个数组里面存储多个人的姓名,附带着联系电话。如果我要找一个联系人的联系电话,可以通过下标值快速地找到。

四、数据的存储。

:比如一个单词(字符串),转成数字作为下标值进行存储。

方法一:ASCII编码之和

       但是这里有一个局限性,因为字符串是由多个字符组成的,所以要将字符串转为对应的数字,就需要有一个相应的函数将之进行转换。我们将之缩小化,空格为0,a为,1,b为2,以此类推,如give 可以转换成数字:7+9+22+5=43 。但是这里又有一个新的问题:还有很多的单词与我们转换成的give对应的数字相同,比如tin、was等等。

所得的数字小会产生高频率的重复情况。

方法二:幂的连乘

可以用幂的连乘来表示下标值:

比如:5987 = 5 * 10^3 + 9 * 10^2 + 8 * 10 + 7

上面的方法一就可以用7 * 27^3 + 9 * 27^2 + 22 * 27 + 5 = 144941‬    (很大程度的保证了数据的唯一性,即不会与别的单词有重复)

所得的数字大占用的空间很大,造成很大的空间浪费。

五、方法二改进——哈希化

例如,如果要存储10000个单词,可能需要定义一个长度为10000的数组,但是实际上,往往需要一个更大的空间来存储这些单词,因为我们不可能每一个位置都能够有一个单词,也可能会有两个或者两个以上的单词。

这时候就要对过大的数字进行压缩化。

哈希化:将大数字转化成数组范围内下标的过程

哈希函数:大数字在进行哈希化所要实现的代码放在一个函数中,这个函数就是哈希函数。

哈希表:通过哈希函数将元素插入到数组中,所得到的结构的封装,称之为哈希表。

方法:取余操作符,作用是得到一个被另外一个数整除后的余数

取余操作符的具体实现:

这里举了一个例子,利用数字与10求余将0~100的数字压缩成0~9。

问题:即使空间很大,数字也很大,所得到的压缩值也是有可能重复的,比如上图中的0与100以及25与35,他们的压缩值都是5,这就产生了冲突。

六、解决冲突

      冲突是不可避免的,我们只能去解决冲突。即使发生冲突的可能性比较小,但是我们还是需要考虑这种情况。

一:链地址法(拉链法)。

数组里面又是一个数组或者是链表。

选择数组还是链表,取决于具体的业务选择,当发生冲突时,如果需要插入的位置是在首端的话就用链表;需要插入到末尾的话就可以考虑用数组或者是链表都可以。

二:开放地址法。

1.线性探测法:线性的查找空白的地方进行存放。

  • 对于线性探索法的插入

图中的新元素2经过哈希化得到的下标值是2,对应的位置已经存在了22这个元素,因此,元素2将其下标值加1,找到下标值为3这个位置,发现没有元素,就将元素插。当需要插入52这个元素时,其哈希化的下标值也是2,进而查找下一个,直到找到下标值为5的位置上插入。

  • 对于线性探索法的查找

查找已经存在的元素:当我们要查找52这个元素,我们就可以利用哈希化的下标值找到对应的位置,如果当前位置不是52,则下标值加1,直到找到下标值为5的位置。

查找不存在的元素:那我们如果我们需要查找32这个元素呢,这时候是不是需要遍历整个哈希表呢,当然不是,因为这样的效率很低,同样的找到下标值为5这个位置时,我们将下标值加1后发现是一个空白位置,那就可以停止查找了,因为按照线性探索法的插入原则,当下一个需要存储的32本应该存放在下标值为6的位置,现在这个位置为空,说明该哈希表中不存在32这个元素。

情况排查:现如果我们将元素2移除,那下标值为3的位置应该设置成什么呢?如果设置成null,会不会给查找造成错误了呢?

假设需要查找52这个元素,那查找到位置3时,如果其在删除元素时将之设置成null的话,那按照刚刚查找的方法就会停止,但是52确实存在该数组中。这就涉及到线性探索法的删除操作。

  • 对于线性探索法的删除

因为将它设置成null可能会影响我们之后查询其他操作,所以通常在删除一个位置的数据项时需慎重,这时候不可以将删除元素原来的额位置设置为空,我们可以将它进行特殊处理,比如这里可以设置成-1,这样的话,往后对查找会有一个区分。

  • 线性探索法存在的缺点

对于下面一个已存放好几个数据的数组来说

依次存放31-32-33-34-35-36,都会存放到一个连续的对应下标值位置,意味着下标值1-2-3-4-5-6的位置都有元素,当后续又存放21时,这时候就要探索多次才能够插入到空白的位置,我们称这种一连串填充单元叫做聚集,这种聚集现象会影响哈希表的性能,无论是插入、查询还是删除都会影响。

2.二次探测法,第一次散列产生哈希地址冲突,经过另外一个散列函数(哈希函数)对冲突结果进行处理。

     该方法可以理解为对线性探测法的改进,针对线性探测法所产生的的聚集现象,不再采用下标值加1的方式进行探索,而是通过另一个散列函数对冲突结果进行处理。

二次探测主要优化的是探测时的步长,如果下标值从x开始,那么线性探测就会是:依次探测,而二次探测则是,这样就可以一次性探测比较长的距离,从而避免了这一方面所带来的聚集问题。扩展为两边查找的方法

  • 二次探测法的缺点

任何一种方法都会有其优点,也会有其缺点,二次探测法在很大程度上改善了步长中出现的聚集问题,但还是会有一种情况就是,当上述插入的元素是11或者是61时,它们通过哈希化得到的结果都是1,那么按照二次探索,所要探索的位置就会重叠,这样就依次增加了其探索次数。也就是说这种情况下会造成步长不一的一种聚集,也会影响效率。

3.再哈希法。

     解决二次探测遗留的问题,我们就可以想到,有什么方法可以让二次哈希化的哈希函数能够不是每次固定一个有规律的步长呢?

二次哈希化的两个要求:

  • 第一个哈希函数不同,因为这样会回到原来的位置。
  • 不能输出0,这样的话步长就为0了,会让接下来的探测在原地踏步,算法就会进入死循环。

那么就有一个公式:stepSize = constant - (key % constant) (其中constant是质数并且小于数组的容量

再哈希是把关键字用不同的哈希函数再做一遍哈希化,用这个结果作为步长,对于指定的关键字,步长在探测的过程中是不变的,也就可以理解为不同的关键字可以使用不同的步长,并且步长是可以控制。

4. 随机探测再散列。

       di=伪随机序列

三: 再散列法

      Hi=RHi(key),i=1,2,…,k RHi均是不同的散列函数,即在同义词产生地址冲突时计算另一个散列函数地址,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间。

四: 建立一个公共溢出区

     在添加新的数据项时,只要哈希函数得到的地址发生了冲突,就都填入溢出表中。

猜你喜欢

转载自blog.csdn.net/weixin_42339197/article/details/99216531
今日推荐