【问题标题】:Yet another ordered map vs. unordered (hash) map question另一个有序映射与无序(散列)映射问题
【发布时间】:2011-11-19 23:17:09
【问题描述】:

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

当然,我们被引导相信一个设计良好的散列容器可以在 O(1)(恒定)时间与 O(logN) 时间内搜索排序容器。但是隐含的常数是什么?哈希图在什么时候将地图丢失在尘埃中?特别是如果key是整数,key搜索的开销很小,所以map中的常量会很小。

尽管如此,几乎每个人都认为散列容器更快。已经进行了大量的实时测试。

发生了什么事?

【问题讨论】:

  • 哎呀,十倍长....不是说它有多大区别
  • 你使用了错误的日志——对于基于二叉树的地图,应该是基数 2 而不是基数 10...

标签: c++ hash map


【解决方案1】:

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

确实,遍历地图只需要 O(log n) 个“步骤”。但是看一下常数因子,请注意该对数的基数是 2,而不是 10;并且二叉树可能实现为红黑树,一般情况下不完全平衡。 (如果有记忆,它可以比 log2(n) 深 2 倍。)

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

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

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

【讨论】:

  • O(log10 N) = O(lg2 N)log10Nlg2N 仅相差 3 左右的常数因子。
  • @quasiverse:当然。红黑树的 2 倍因子也“只是”一个常数因子。我的观点是,在这种情况下,这些常数很重要。这些不仅仅是算术运算;它们是随机内存访问,在现代系统上非常缓慢(相对而言)。
  • 我不会在运行时评论它。只是你说有O(log n) "steps",在那种情况下没有区别。如果你说,例如2 log n steps,我不会发表评论。
  • @quasiverse:好点。我写它的方式听起来好像我不知道根据对数改变基数只是一个常数因素。我改变了我的措辞。谢谢。
【解决方案2】:

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

使用哈希表需要考虑两种重要的最坏情况:

  1. 所有数据项生成相同的哈希索引,因此所有数据项最终都在同一个哈希桶中 - 在这种情况下查询哈希映射将使用 O(N)
  2. 数据生成的哈希索引分布极其稀疏,因此大部分哈希桶都是空的。在这种情况下,您仍然可以使用O(1) 查询时间结束,但空间复杂度基本上可以变得无限。

另一方面,二叉搜索树(至少是大多数标准库实现中使用的红黑树)享有最坏情况下的O(log(N)) 时间和O(N) 空间复杂度。

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

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

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

希望这会有所帮助。

【讨论】:

  • 感谢您的快速回复。也许我的问题太含糊了,但是搜索 100 亿个条目映射(在 RAM 中)将花费 10 倍于搜索 10 个条目映射的时间,这对吗?
  • @Mike:渐近地,是的,这是正确的。虽然操作的确切数量是实现定义的(大哦符号显然隐藏了任何常数因素)。 BST 通常也会有一个允许的“平衡”范围,因此在任何特定时间完成的操作的确切数量取决于树中当前的不平衡量 - 这个常数因子被吸收到 O(log(N))...跨度>
【解决方案3】:

正如您正确指出的那样 - 魔鬼在细节中(在这种情况下 - 常量)。您必须对代码进行基准测试以确定哪个对您更有效,因为 O-Notation 在您处理实际约束时适用于无穷小值。

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

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

同样,神奇的词是基准

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-30
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多