又一个有序地图与无序(哈希)地图问题

时间:2011-09-19 05:14:50

标签: c++ hash map

这是一个非常天真的问题,但我找不到对它的明确讨论。 每个人都同意使用哈希来处理只有10个元素的地图容器,这样就太过分了。有序的地图会快得多。一百个;一千个,等等,地图应按logN进行缩放,其中N =地图中的#对。所以对于一千,它需要三倍的时间;百万,六倍; 100亿,是时间的九倍。

当然,我们可以相信,对于已排序的容器,可以在O(1)(常数)时间与O(logN)中搜索设计良好的散列容器。但隐含的常数是什么?哈希地图在什么时候丢失地图?特别是,如果键是整数,则键搜索的开销很小,因此映射中的常量会很小。

尽管如此,关于每个人都认为散列容器更快。已经完成了大量的实时测试。

发生了什么事?

3 个答案:

答案 0 :(得分:3)

正如您所说,基于哈希的地图确实有可能比二叉搜索树渐近更快,查询时间为O(1) vs O(log(N)) - 但这完全取决于在允许的输入数据分布上使用的散列函数的性能。

使用哈希表可以考虑两个重要的最坏情况:

  1. 所有数据项都生成相同的哈希索引,因此所有项目都以相同的哈希桶结束 - 在这种情况下查询哈希映射将采用O(N)
  2. 数据生成的哈希索引的分布非常稀疏,因此大多数哈希桶都是空的。在这种情况下,您仍然可以获得O(1)查询时间,但空间复杂度在极限情况下基本上可以无限制。
  3. 另一方面,二进制搜索树(至少在大多数标准库实现中使用的红黑树)享有最坏情况O(log(N))时间和O(N)空间复杂度。

    所有这些(在我看来)的最新结果是,如果你对输入数据有足够的了解来设计一个“好”的哈希函数(没有太多的冲突,就不会产生过多的稀疏散列桶的分配)使用散列映射通常是更好的选择。

    如果无法确保哈希函数的性能超过预期输入,请使用BST。

    一个人变得比另一个更好的确切点完全取决于问题/机器。

    希望这有帮助。

答案 1 :(得分:1)

正如你所正确指出的那样 - 魔鬼在细节中(在这种情况下是常数)。您必须对代码进行基准测试,以确定哪种代码对您更有效,因为 O-Notation 是针对无穷小的值,而您正在处理现实世界的约束。

如果它确实是O(1)(即:has函数确实非常好)并且散列函数计算相对较快(开始时) - 不依赖于输入的大小,散列会更快)。

地图上的开销是遍历树,而键比较可能或多或少快(整数更快,字符串更慢),遍历树总是依赖于输入(树深度)。对于较大的树,请考虑使用B-Trees而不是标准映射(在C ++中通常使用红黑树实现)。

同样,神奇的词是基准

答案 2 :(得分:1)

哈希映射更快的确切点将取决于机器。

确实只需要O(log n)“步”来遍历地图。但是暂时看一下常数因素,请注意该日志的基数是2,而不是10;并且二叉树可能实现为红黑树,通常不是完美平衡的。 (如果内存服务,它可能比log2(n)深2倍。)

然而,真正推动差异的是有序地图的差的地点。这些O(log n)步骤中的每一步涉及不可预测的分支,这对于指令流水线是不利的。更糟糕的是,它涉及追逐一个随机指向内存的指针。现代CPU的经验法则是:“数学很快;记忆很慢。”这是一个值得记住的好规则,因为每一代人都会变得更加真实。 CPU核心速度通常比内存速度提高得快。

因此,除非您的地图足够小以适应缓存,否则这些随机指针解除引用非常对整体性能不利。计算哈希只是数学(因此很快),追逐O(1)指针比追逐O(log n)更好;对于大n来说通常要好得多。

但同样,哈希表优势的确切点将取决于具体的系统。