【问题标题】:map or hashmap for efficient lookup of generic strings?map 或 hashmap 用于有效查找通用字符串?
【发布时间】:2012-08-11 02:02:19
【问题描述】:

我需要能够存储和查找通用字符串。我对字符串的内容了解不多,2/3 多一点是人类语言单词,其余的则更接近 UUID 或数字/字母组合。我知道任何特定的分组都是不变的(即,如果它有一些人类词,它将是所有人类词,如果它有一些 UUID,则所有内容都是 UUID 等)。

我需要决定是否应该将这些数据放在地图或哈希图中,以获得最佳的平均查找率。我倾向于使用 O(log n) 运行时说 map,因为当我对字符串的输入格式知之甚少时,我不相信我可以为字符串进行适当的有效散列。有什么想法会更好吗?

编辑: 我忘记了一个关键方面。我不知道字符串的长度,因此担心长字符串的内存使用量可能会增长过大。如果我使用散列方法,我会做一些事情,在 X 个字符之后,散列不会基于每个字符进行散列,以避免内存消耗太大。

我真正想要的是一个哈希映射实现,它可以将“桶”中的多个值按有序的方式排序,这样它就可以提供(log N)桶的搜索;但我认为 Stardrd C++ 中不存在它,也不值得从头开始编写。

pps。数据接近静态。我偶尔会不得不将它添加到列表中,这很罕见,我愿意接受缓慢的写入时间。我只关心查找时间。

【问题讨论】:

  • 两者都试试,分析和比较?

标签: c++ string hash dictionary unordered-set


【解决方案1】:

很难做出单一的推荐。这取决于几个权衡(迭代类型、内存与查找)。在整个过程中,我假设您可以使用 C++11 编译器(或等效的 Boost 或 TR1 库)。

如果插入/查找时间对您来说最重要,我肯定会使用std::unordered_set(请参阅reference)和std::hash<std::string>(请参阅reference)。 insertionlookup 平均为 O(1)(摊销常数)。如果

请注意,无序散列容器不允许您按排序顺序进行迭代。所以如果你想要排序迭代,那么你可以使用有序容器std::set<std::string>,但是你付出的代价是O(log N)lookup/insertion。

内存限制更难分析。首先,有序容器std::setstd::map 大约需要每个元素开销3 个字 来维护允许有序迭代的树结构。然而,无序散列容器有一些备用容量,因为散列容器在满负载时运行非常糟糕。

#include <iostream>
#include <functional>
#include <string>
#include <unordered_set> // or <set> for ordered lookup

int main()
{
    // or std::set<std::string> for ordered lookup
    std::unordered_set<std::string> dictionary; 

    std::string str = "Meet the new boss...";
    dictionary.insert(str);
    auto it = dictionary.find(str);

    std::cout << *it << '\n';
}

Ideone 上的输出。如果您还想将Valuestd::string 一起存储,则可以使用具有相同哈希函数的std::unordered_map&lt;std::string, Value&gt;std::map&lt;std::string, Value&gt;

结论:最好根据上述权衡取舍来衡量最适合您的应用程序的方法。

【讨论】:

  • 如果你有 TR1 的实现,你也可以使用 unordered_set 和 unordered_map。我很确定他们也在 Boost 中。
  • 我觉得你的后续回答忽略了无序集更重要的内存成本。是的,地图中单个元素的开销更大,但不同之处在于树只包含有效数据。使用哈希映射解决方案,将存在根本不包含任何数据的存储桶,尽管它们是空的,但它们仍然会消耗内存。在您的哈希函数给出备用/扩展结果的情况下,您必须在两个选项之间进行选择。一个小的 hashmap 会以大桶结束,并且有可能接近最坏情况 N 次,或者因为空桶而丢失一些内存
  • @dsollen 这是一个很好的观点。我不知道std::unordered_map 的确切内存扩展策略。我认为它可能与std::vector 相同(容量约为当前大小的 1.5 倍)。 OTOH,如果您有很长的字符串,那么 std::map 仍然必须存储这些字符串,而不是 32 位或 64 位哈希键。
  • 我认为字符串必须存储在有序和无序中。想象一下,我有一个通向存储桶 A 的哈希 A。在存储桶中,我存储了两个值(a 和 b)。如果我想查找一个值,我可以使用一个字符串来获取一个散列并使用该散列找到存储桶 A。但我仍然需要确定值 a 或 b(或两者都不是)是应该返回的值。这样做的唯一方法是查看值 a 和 b 的完整非散列字符串键,以查看它们是否与我的 fetch 键匹配。所以在不知道确切暗示的情况下,我会假设除了哈希之外,哈希图仍然必须存储字符串。
  • @dsollen 你是对的,我在之前的编辑中已经改变了这一点。每个元素只有 3 个字与备用容量在内存权衡中起作用
【解决方案2】:

除了 std::set、std::map、std::unordered_set 和 std::unordered_map - 我还会考虑研究 Tries 看看它们是否更适合:

http://en.wikipedia.org/wiki/Trie

【讨论】:

  • 尝试看起来确实很有趣并且可能值得考虑,但我不愿意通过从头开始编写数据结构来花费太多精力来进行可能过早的优化。 C++(或 boost)是否已经包含了一个 Try 实现?
  • @duedl0r:我不确定这是否正确。你是怎么得出这个结论的?你有参考吗?
  • 只是一个没有根据的猜测......不知何故你必须存储指针,所以数据的每个字节都会大 5 倍(4 字节指针)。当然有压缩的方法,但是我不敢实现
  • @duedl0r:你还没有考虑清楚。每个字符串都有一个指针,就像在二叉树或哈希表中一样,只是它指向字符串唯一的后缀而不是整个字符串。此外,由于公共前缀只存储一次,总体而言它实际上比二叉树或哈希表占用的空间更少。
【解决方案3】:

CedarHAT-TrieJudyArray 非常棒,你可以找到基准 here

【讨论】:

    【解决方案4】:

    您可能想看看基准测试:http://www.dotnetperls.com/sorteddictionary 尽管发生冲突,但它在实际应用中出现 Dictionary 比 SortedDictionary 更好。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-13
      • 2014-07-25
      • 2018-03-31
      • 1970-01-01
      • 2011-04-28
      • 2012-03-06
      • 1970-01-01
      • 2020-11-22
      相关资源
      最近更新 更多