又是一个有序映射vs.无序(散列)映射的问题
Yet another ordered map vs. unordered (hash) map question
这是一个非常幼稚的问题,但我找不到关于它的明确讨论。每个人都同意对只有10个元素的map容器使用散列是多余的。有序的地图会快得多。用一百;地图应该按logN缩放,其中N= #对地图。所以对于1000个,需要三倍的时间;一百万,六倍长;100亿,9倍长。
当然,我们相信一个设计良好的散列容器可以在O(1)(常数)时间内搜索到,而一个排序容器可以在O(logN)时间内搜索到。但是隐含常数是什么呢?在什么时候哈希映射在尘埃中丢失了映射?特别是,如果键是整数,则键搜索的开销很小,因此map中的常数将很小。
然而,几乎每个人都认为散列容器更快。已经进行了大量的实时测试。
怎么回事?
正如你所说的,基于哈希的映射确实有可能比二叉搜索树渐进地快,在O(1)
和O(log(N))
的查询时间上——但这完全取决于在允许的输入数据分布上使用的哈希函数的性能。
对于哈希表,有两种最坏的情况需要考虑:
- 所有数据项生成相同的哈希索引,因此所有数据项最终在相同的哈希桶中-在这种情况下查询哈希映射将采取
O(N)
。 - 数据生成的哈希索引分布非常稀疏,因此大多数哈希桶是空的。在这种情况下,您仍然可以以
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会更好。但是,哈希表占据主导地位的确切点将取决于特定的系统。
- 递归无序映射
- 正在将无序映射设置为无序映射的值
- 智能指针作为无序映射键,并通过引用进行比较
- 打印无序映射的第二个元素,即集合
- 由并发无序映射查找线程调用的函数是否安全?
- 如何按值对无序哈希映射进行排序
- 将大数字(10-12 位数字)存储在无序映射中<字符串,整数>
- 使用无序映射在STL中存储键值对
- 为C++中的无序映射获取给定输入键的错误值
- 将大型对象存储在无序映射中是否效率低下
- 在无序映射的结构化绑定中推导类型
- 使用无序映射进行错误索引
- 将一个向量对放在一个无序映射与一个映射中
- 如何在c++中修改无序映射中的每个值
- C++:使用一对(cpp_int,int)整数作为无序映射中的键(其中cpp_int是boost多精度整数)
- 将 [] 索引到无序映射时出现段错误
- 以std::字符串作为关键字,按字典顺序对一个无序映射进行排序
- 无序映射存储桶的节点大小
- 在无序映射<字符串上使用 find(),向量<string>>与 C++ 中的有序映射花费相同的时间
- 以三个无符号字符为关键字的无序映射