JavaScript pt 2中的数据结构-哈希表

上周,我们探讨了二进制搜索树的数据结构以及如何在JavaScript中实现它们。 本周,我们将介绍另一种称为哈希表的数据结构也称为哈希图 。 哈希表使我们可以按固定时间或O(1)查找和插入数据。 在许多情况下,散列表比搜索树或其他表查找结构更有效。 您可以在此处找到我的哈希表构建的完整存储库以及其他一些数据结构— https://github.com/mega0319/data-structures

哈希表实现了关联数组抽象数据类型 -这基本上意味着它将键映射到值。

它们如何工作?

哈希表将以一定数量的“存储桶”启动。 假设我们启动了一个大小为32的数组。将这些“存储桶”视为数组中的空插槽。 0-31中的每个索引都可以视为“存储桶”。

哈希表使用哈希函数来确定存储数据的位置。 哈希函数将从我们打算存储的数据中获取一个密钥 ,并返回一个索引。 听起来很简单,但是散列函数可能非常复杂!

在为哈希表构建哈希函数的情况下,我们希望在给定相同输入的情况下,该函数将返回相同的索引。 例如,假设我要存储一个为“ rocky”的节点 。 每次我通过哈希函数运行键“ rocky”时,它都应返回相同的索引。 当我们插入数据时,我们得到一个16的索引。然后,我们寻找索引为16的存储桶,并将数据存储在该存储桶中。 当我们想在哈希表中查找数据时,我们通过哈希函数运行相同的键,索引为16,然后搜索该特定存储桶以查看其是否存在。 这是常量或O(1)查找背后的原因。

理想情况下,我们设计的哈希算法使每个键都返回唯一索引,并将数据均匀分布在存储桶阵列中,但是大多数哈希算法在设计时都存在缺陷。 如果两个不同的键返回相同的索引会怎样? 在这种情况下,我们将有一个称为hash hash的东西。

不是这种碰撞

根据哈希函数的复杂程度,可以将冲突最小化到一定程度,但这是不可避免的。 即使我们有一百万个存储桶并想存储2000个不同的密钥,我们还是有可能发生某种冲突。

根据概率论生日问题 ,即使使用完美的哈希算法,也有95%的机会会发生冲突。 生日问题或生日悖论指出,如果随机选择n个人,那么他们中的某对将具有相同的生日。 只有23个人的机率约为50%,而有70个人的机率则为99%。 当您有367个人时,实际上的概率就会增加到100%,因为可能有366个生日,包括2月29日。 说得通…

有几种方法可以解决此冲突问题,我将展示一种解决冲突的方法,但是在本博客中我们不会做太多深入的介绍。 话虽如此,让我们开始在JavaScript中构建哈希表

因此,我们需要的第一件事是几个构造函数

哈希表和哈希节点构造函数

第一个构造函数用于哈希表本身。 它需要一个整数来指定我们想要用于哈希表的存储桶的大小或数量。 如您在第11行中看到的,我已经初始化了一个具有30个存储桶的哈希表。 它还将跟踪哈希表中当前具有的存储桶数。

第二个构造函数用于哈希节点。 该节点将接收一个键和一个值。 您现在可以忽略下一部分,我们稍后将介绍。

接下来,我们将构建哈希函数。

散列函数

让我们来看一下哈希函数的工作原理。 请记住,该算法的唯一目的是获取一个键并返回一个索引,以便在给定特定的input(key)的情况下,它将始终返回相同的output(index)。 关键是一个字符串。 我们将遍历该字符串,并使用JavaScript的charCodeAt函数查找每个字母的字符代码。 找到字符代码后,我们会将其添加到总数中。 我们最终存储了一些数字。 然后,我们采用该数字并通过存储桶数对其进行修改。 此步骤确保我们返回0到29之间的索引,这是我们想要的,因为我们有30个存储桶。 对于此步骤,我们将始终修改n个存储桶,以获取从0到n-1的索引。 就是这样! 现在我们有了一个散列函数,它将返回一个索引。

下一站,插入!

插入功能

这是事情开始变得有趣的地方。 我们的插入函数将接受一个键和一个值。 键和值参数将用于创建新的哈希节点。 但是,在创建节点之前,我们必须首先弄清楚将节点放置在何处……

我们的第一步是找到索引。 在第32行,您可以看到我们正在调用哈希函数,将其传递给键并获得索引并将其存储在变量中。 这是我们要放置节点的索引或存储桶位置。

接下来,我们检查该索引处的存储桶是否为空。 如果存储桶为空,我们可以创建一个新的哈希节点并将其放置在该位置。 简单!

如果铲斗不空怎么办? 天哪,哈希冲突...

真的不是那么严重

不要烦恼! 在我们假设是冲突之前,我们的插入函数将检查存储桶中的键是否与传入的键相同。如果是,则可以假设我们只是在更新值。 在这种情况下,第36行将只是将该存储桶中的值设置为传入的值。如果不是这种情况,则…

在碰撞中链接节点

我们将链接节点。 这是节点的下一个属性起作用的地方。 在第38行,我们开始遍历该存储桶中的节点链。 首先,我们在该索引中将存储桶设置为currentNode。 使用while循环,我们检查兔子洞的深度。 在整个循环中,我们将继续检查是否碰巧找到具有相同键的节点,然后编辑该键的值。 如果不是,我们将向下遍历,直到找到一个节点,其下一个指针设置为null。 这意味着我们已经到达了链中的最后一个节点。 然后,我们将一个新节点附加到链中的该位置。 上面的图像很好地展示了它的外观。

现在我们将构建我们的get函数。

获得功能

该函数的目的是通过我们将作为参数传递的键在哈希表中搜索并查找节点。 与插入函数类似,我们要做的第一件事是通过哈希函数运行密钥以获取索引。 一旦获得索引,就可以在恒定时间内运行查找。 我想强调一下这样做的原因。

快速刷新-数组中的索引本质上引用了元素在内存中的位置。 看一下这个例子。

let arr = [1,2,3,4,5,6,7,8,9] arr[3] // 4

当我们创建一个数组时,我们的元素被存储在内存中的某个地方。 由于数组本身会跟踪元素开始的内存地址,因此索引有助于计算数组中每个元素的地址。 此计算在恒定时间或O(1)中完成。

因此,因为我们能够使用哈希函数获得特定的索引,所以我们能够检查该键是否快速存在。 有一个警告。 我们存储在哈希表中的数据越多,获取速度就越慢。 您可以想象在添加数据时会遇到很多冲突。 一旦发生这种情况,我们将不得不更频繁地遍历存储桶中的节点链。 这会将我们的平均查找更改为线性时间或O(n)。

非常类似于我们的插入功能,如果密钥不是存储桶中的第一个密钥,我们将遍历其链以查找密钥。 如果仍然找不到,我们将返回null。

我们的最后一个函数将是retrieveAll。

检索全部

从哈希表中检索每个节点都揭示了它的弱点之一-遍历。 归根结底,我们的存储桶数组是一个数组,因此我们必须遍历每个存储桶,然后针对每个存储桶向下遍历节点链以接触每个可能的节点。 该函数的运行时间为平方或O(n²)。

与其他所有基本数据结构一样,构建哈希表是一个很好的练习,可以通过测试其优势并发现其劣势来了解它们的工作方式。

希望对您有所帮助!

结束

From: https://hackernoon.com/data-structures-in-javascript-pt-2-hash-tables-8a6cc8ae3bd3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值