【问题标题】:How to improve the complexity of HashMap iteration?如何提高HashMap迭代的复杂度?
【发布时间】:2023-04-01 23:58:01
【问题描述】:

我实现了一个自定义的 HashMap 类(在 C++ 中,但没关系)。实现很简单 -

  • 一个大数组包含指向项目的指针。
  • 每个项目都包含键值对和指向项目的指针(在发生键冲突时形成链表)。
  • 我还为它实现了一个迭代器。

我对迭代器进行递增/递减的实现效率不是很高。从当前位置开始,迭代器扫描哈希数组以查找下一个非空条目。当地图人口稀少时(这将是我的用例),这是非常低效的。

谁能建议一个更快的实现,而不影响插入和查找等其他操作的复杂性?我的主要用例是查找,次要是插入。甚至不需要迭代,我只是为了学习而了解这一点。

PS:为什么我实现了一个自定义类?因为我需要找到具有一定容错性的字符串,而我看到的现成哈希映射只提供完全匹配。

编辑:为了澄清,我说的是增加/减少已经获得的迭代器。是的,这主要是为了遍历整个地图。

在我的例子中,字符串(键)中的错误是由 OCR 错误引起的。所以我不能使用用于检测打字错误的错误处理技术。拳字出错的几率和上一字差不多。

另外,我的键总是字符串,确切地说是一个词。条目数将少于 5000。所以 2^16 的哈希表大小对我来说就足够了。即使它仍然会人口稀少,但没关系。

我的哈希函数:

哈希码大小为 16 位。

字长的前 5 位。 ==> 最大可能的密钥长度 = 32。考虑到密钥是单个单词,这是合理的。

字符代码总和的最后 11 位。我只存储英文字母字符,不需要区分大小写。所以 26 个代码就足够了,从 0 到 25。所以一个 32 'z' = 25 * 32 = 800 的键。正好在 2^11 之内。如果将来需要,我什至可以添加区分大小写的功能。

现在,当您将包含错误的密钥与正确的密钥进行比较时, 用“你好”说“地狱” 1. 密钥长度大致相同 2. 它们的字符总和会因丢弃/添加/扭曲字符的总和而有所不同。

在哈希码中,由于前 5 位是长度,整个表对于每个可能的键长度都有固定的部分。所有部分的大小相同。第一部分存储长度为 1 的键,第二部分存储长度为 2 的键,依此类推。

现在 'hello' 存储在第 5 部分,因为长度为 5。'当我们尝试查找 'hello' 时, 'hello' 的哈希码 = (length - 1) (sum of chars) = (4) (7 + 4 + 11 + 11 + 14) = (4) (47) = (00100)(00000101111)

类似地,'helo' 的哈希码 = (3)(36) = (00011)(00000100100)

  1. 我们跳到它的桶里,却找不到它。
  2. 所以我们尝试检查一个扭曲的字符。这不会改变长度,但会将字符的总和最大 -25 更改为 +25。所以我们从向后的 25 个位置搜索到向前的 25 个位置。即,我们在同一部分检查从 (36-25) 到 (36+25) 的总和部分。我们不会找到它。
  3. 我们检查其他字符错误。这意味着正确的字符串将只包含 3 个字符。所以我们进入第三部分。现在由于额外的字符,字符的总和最多会增加 25,它必须得到补偿。因此,在第三部分中搜索适当的 25 个位置 (36 - 0) 到 (36 - 25)。我们还是没有找到。
  4. 现在我们考虑缺少字符的情况。所以原始字符串将包含 5 个字符。而哈希码的第二部分,即原始字符串中的字符之和,会多出 0 到 25 倍。所以我们在第 5 部分搜索相应的 25 个桶,(36 + 0) 到 (36 + 25)。现在由于 47('hello' 的总和部分)在这个范围内,我们将找到哈希码的匹配项。 Ans 我们也知道这个匹配是由于缺少字符。因此,我们比较允许 1 个缺失字符容差的键。我们得到了一场比赛!

实际上,这已被实施以允许键中出现多个错误。 它还可以优化为第一部分仅使用 25 个位置(因为它只有一个字符)等等。 此外,检查 25 个位置似乎有点过分,因为我们已经知道键的最大和最小字符。但是如果出现多个错误,它就会变得复杂。

【问题讨论】:

  • 我不明白,当你试图找一个值的时候,你不应该马上去给定key的数组中保存的第一个Item,然后遍历一个链表吗?如果你必须遍历整个数组,那么要么你的数组太小,要么你的散列函数不是很好(这种情况发生的唯一方法是如果有 tons 的冲突)。跨度>
  • @Jared 我认为(很可能是错误的)他说的是迭代整个哈希映射,而不仅仅是一个冲突链。很高兴知道为了更清楚,我同意。
  • 你是说你想要像"hello""hwllo"这样的东西映射到相同的哈希值(因为它们是相似的——注意'e'和'w'很容易不小心根据他们在 QWERTY 键盘上的位置相互键入)。因此,您想要快速查找本质上对相似字符串进行分组?因为如果您说查看每个字符串并进行比较(并找到最接近的字符串),那么您并没有描述任何可以远程解释为哈希表的内容。
  • @Jared 我已经更新了这个问题并进行了澄清。我想改进迭代整个哈希图。我不希望“hello”和“hwllo”具有相同的哈希值,但是当存储“hello”并尝试查找“hwllo”时,我应该能够有效地到达“hello”。这部分已经实现,现在在问题中进行了更新。
  • @WhozCraig 是的,你的想法是对的。请查看更新后的问题以了解详细实施情况。

标签: java c++ data-structures iterator hashmap


【解决方案1】:

您提到了字符串的“容错”。为什么不将“容差”内置到哈希函数本身中,从而消除迭代的需要。

【讨论】:

  • 是的,这就是我所做的。我已经用细节更新了这个问题。实际上我不需要遍历整个数组。但想知道如何加快速度。
【解决方案2】:

你可以走 Javas LinkedHashMap 类的路。它还通过使其成为双向链表来为哈希图添加有效的迭代。

条目是键值对,具有指向上一个和下一个条目的指针。 hashmap 本身具有大数组以及链表的头部。

两种数据结构的插入/删除都是常数时间,搜索通过哈希图完成,迭代通过链表完成。

【讨论】:

  • 谢谢。我想到了这个,但是插入删除怎么可能是常数时间呢?它只会在地图上保持不变。您还必须在链接列表中搜索要插入/删除的键。 (当条目数量较少时,这不会有问题)
猜你喜欢
  • 2016-02-23
  • 2019-04-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-12
  • 1970-01-01
  • 1970-01-01
  • 2020-05-14
相关资源
最近更新 更多