又是一个有序映射vs.无序(散列)映射的问题

Yet another ordered map vs. unordered (hash) map question

本文关键字:映射 无序 vs 问题 散列 一个      更新时间:2023-10-16

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

当然,我们相信一个设计良好的散列容器可以在O(1)(常数)时间内搜索到,而一个排序容器可以在O(logN)时间内搜索到。但是隐含常数是什么呢?在什么时候哈希映射在尘埃中丢失了映射?特别是,如果键是整数,则键搜索的开销很小,因此map中的常数将很小。

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

怎么回事?

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

对于哈希表,有两种最坏的情况需要考虑:

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

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

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

如果你不能保证哈希函数在预期输入上的性能,可以使用BST。

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

正如您正确指出的那样,细节决定成败(在本例中是常量)。你必须对你的代码进行基准测试,以决定哪一个对你更有效,因为 o符号是在处理现实世界的约束时用于无穷小的值。

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

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

同样,这个神奇的词是基准测试

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

确实只需要O(log n)"步骤"来遍历地图。先看一下常数因子,注意对数的底数是2,而不是10;二叉树可能是用红黑树来实现的,它通常不是完美平衡的。(如果内存足够,它最多可以比log2(n)深2倍)

然而,真正导致差异的是有序映射的较差的局部性。这O(log n)步中的每一步都涉及到一个无法预测的分支,这对指令管道来说是不利的。更糟糕的是,它涉及到追踪一个指向内存的随机指针。现代cpu的经验法则是:"数学运算很快;记忆很慢。"这是一条值得记住的好规则,因为每代人都会变得更加正确。CPU核心速度通常比内存速度提高得快。 所以,除非你的映射足够小,可以放进缓存,否则那些随机的指针解引用对整体性能来说是非常不利的。计算哈希只是数学运算(因此速度很快),追踪O(1)个指针比追踪O(log n)个指针要好;通常对于大n会更好。

但是,哈希表占据主导地位的确切点将取决于特定的系统。