为什么std::map
实施为red-black tree?
那里有几个平衡binary search trees(BST)。选择红黑树的设计权衡是什么?
答案 0 :(得分:107)
两种最常见的自平衡树算法可能是Red-Black trees和AVL trees。为了在插入/更新之后平衡树,两种算法都使用旋转的概念,其中树的节点被旋转以执行重新平衡。
虽然在两种算法中插入/删除操作都是O(log n),但在红黑树的情况下,重新平衡旋转是O(1)操作,而使用AVL则是O(log n)操作,使红黑树在重新平衡阶段的这方面更有效,并且是更常用的可能原因之一。
Red-Black树在大多数集合库中使用,包括Java和Microsoft .NET Framework的产品。
答案 1 :(得分:42)
这实际上取决于用法。 AVL树通常有更多的重新平衡轮换。因此,如果您的应用程序没有太多的插入和删除操作,但是在搜索时权重很大,那么AVL树可能是一个不错的选择。
std::map
使用红黑树,因为它在节点插入/删除和搜索的速度之间得到了合理的权衡。
答案 2 :(得分:23)
AVL树的最大高度为1.44logn,而RB树的最大高度为2logn。在AVL中插入元素可能意味着在树中的某一点重新平衡。重新平衡完成插入。插入新叶后,必须更新该叶的祖先,或者直到两个子树深度相等的点。必须更新k个节点的概率是1/3 ^ k。重新平衡是O(1)。删除元素可能意味着不止一次重新平衡(最多可达树的一半深度)。
RB树是4阶B树,表示为二叉搜索树。 B树中的4节点导致等效BST中的两个级别。在最坏的情况下,树的所有节点都是2节点,只有一个3节点链到一个叶子。那片叶子距离根部2logn。
从根到插入点,必须将4个节点更改为2个节点,以确保任何插入都不会使叶子饱和。从插入开始,必须分析所有这些节点以确保它们正确地表示4节点。这也可以在树上进行。全球成本将是相同的。天下没有免费的午餐!从树中删除元素的顺序相同。
所有这些树都要求节点携带有关高度,重量,颜色等的信息。只有Splay树没有这些附加信息。但是大多数人都害怕Splay树,因为它们的结构存在冲突!
最后,树木还可以在节点中携带重量信息,从而实现重量平衡。可以应用各种方案。当子树包含的是另一个子树的元素数量的3倍以上时,应该重新平衡。通过单次或双次旋转再次进行再平衡。这意味着2.4logn的最坏情况。一个人可以逃脱2次而不是3次,这是一个更好的比例,但这可能意味着在这里和那里不会失衡1%的子树。棘手!
哪种树型最好? AVL肯定。它们是最简单的代码,其最差高度最接近logn。对于1000000个元素的树,AVL将最多为高度29,RB 40,以及权重平衡36或50,具体取决于比率。
还有很多其他变量:随机性,添加比例,删除,搜索等等。
答案 3 :(得分:14)
之前的答案仅针对树木替代方案,而红黑可能仅出于历史原因。
为什么不是哈希表?
类型只需要将部分排序(<
比较)用作树中的键。但是,哈希表要求每个键类型都定义了hash
函数。将这些类型要求降至最低对于通用编程非常重要。
设计一个好的哈希表需要对它将被使用的上下文有深入的了解。它应该使用开放式寻址还是链接链接?在调整大小之前它应该接受多少级别的负载?它应该使用一个避免碰撞的昂贵哈希,还是一个粗糙而快速的哈希?
由于STL无法预测哪个是您的应用程序的最佳选择,因此默认需要更灵活。树“只是工作”并且很好地扩展。
(C ++ 11确实添加了带有unordered_map
的哈希表。您可以从documentation看到它需要设置策略来配置其中许多选项。)
其他树怎么样?
红色黑树提供快速查找并且是自平衡的,与BST不同。另一位用户指出其优于自平衡AVL树的优势。
Alexander Stepanov(STL的创建者)说,如果他再次写std::map
,他会使用B *树而不是红黑树,因为它对现代记忆缓存更友好。
此后最大的变化之一就是缓存的增长。 缓存未命中非常昂贵,因此参考的位置更多 现在很重要基于节点的数据结构,具有较低的局部性 参考,没有多大意义。如果我今天正在设计STL,我 会有一组不同的容器。例如,内存中 B * -tree是一个比红黑树更好的选择 一个关联容器。 - Alexander Stepanov
您是否应始终使用红黑树或B *树?
在其他场合,Alex表示std::vector
几乎总是出于类似原因的最佳列表容器。即使对于我们在学校教授的情况(例如从列表中间删除元素),使用std::list
或std::deque
也很少有意义。 std::vector
是如此之快以至于除了大N
之外的所有内容都胜过那些结构。
应用该推理,如果使用std::vector
只有少量元素(数百?)并且线性搜索可能比std::map
的树实现更有效。根据插入的频率,排序的std::vector
与std::binary_search
相结合可能是最快的选择。
答案 4 :(得分:3)
2017-06-14更新:webbertiger在评论后编辑了答案。我应该指出,现在它的答案对我来说好多了。但我把答案保留为附加信息......
由于我认为第一个答案是错误的(更正:不再是两者),而第三个答案是错误的肯定。我觉得我必须澄清一些事情......
最受欢迎的2棵树是AVL和Red Black(RB)。主要区别在于利用率:
主要区别来自着色。 RB树中的重新平衡操作比AVL少,因为着色使您有时可以跳过或缩短具有相对高成本的重新平衡操作。由于着色,RB树也具有更高级别的节点,因为它可以接受黑色节点之间的红色节点(具有~2倍更多级别的可能性)使得搜索(读取)效率稍低......但是因为它是常数(2x),它保持在O(log n)。
如果你考虑修改树的性能(有意义的)VS树的咨询性能(几乎无关紧要),那么在一般情况下优先选择RB而不是AVL是很自然的。
答案 5 :(得分:2)
这只是您实现的选择 - 它们可以作为任何平衡树实现。各种选择都与微小差异相当。因此,任何一个都和任何一样好。