O(logn)
而不是O(1)
,但是值将始终进行排序。从C ++ 11(我认为)开始,有unordered_map
和unordered_set
是使用哈希函数实现的,尽管它们没有进行排序,但是大多数查询和操作都可以O(1)
(平均)在
O(logn)
而不是O(1)
,但是值将始终进行排序。从C ++ 11(我认为)开始,有unordered_map
和unordered_set
是使用哈希函数实现的,尽管它们没有进行排序,但是大多数查询和操作都可以O(1)
(平均)在
Answers:
两种最常见的自平衡树算法可能是Red-Black树和AVL树。为了在插入/更新之后平衡树,两种算法都使用旋转的概念,在该概念中,树的节点被旋转以执行重新平衡。
虽然在两种算法中,插入/删除操作均为O(log n),但对于红黑树,重新平衡旋转是O(1)操作,而对于AVL,这是O(log n)操作,因此红黑树在重新平衡阶段的这方面更有效,这是更常用的可能原因之一。
在大多数集合库中都使用红黑树,包括Java和Microsoft .NET Framework提供的树。
std::map
实现列表,跟踪开发人员,并询问他们制定决策所依据的标准,因此这仍然是猜测。
这实际上取决于用法。AVL树通常具有更多的重新平衡轮换。因此,如果您的应用程序没有太多的插入和删除操作,但是在搜索上占了很大比重,那么AVL树可能是一个不错的选择。
std::map
之所以使用Red-Black树,是因为它在节点插入/删除和搜索的速度之间获得了合理的权衡。
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树,因为它们的结构很随机!
最后,树还可以在节点中携带权重信息,从而实现权重平衡。可以应用各种方案。当一个子树包含的元素数超过另一个子树的元素数的三倍时,应该重新平衡。再次通过单旋转或双旋转进行平衡。这意味着最坏的情况是2.4logn。一个人可以摆脱2次而不是3次,这是一个更好的比率,但是这可能意味着在这里和那里不平衡的子树只剩下不到1%。整rick
哪种树最好?肯定是AVL。它们是最简单的代码,最差的高度最接近logn。对于具有1000000个元素的树,AVL的最大高度将为29,RB的高度为40,并且权重平衡的比例为36或50。
还有许多其他变量:随机性,添加,删除,搜索的比例等。
先前的答案仅针对树的替代方案,而红黑色可能仅出于历史原因而保留。
为什么不使用哈希表?
类型仅要求将<
运算符(比较)用作树中的键。但是,哈希表要求每种键类型都必须hash
定义一个函数。对于通用编程,将类型要求最小化是非常重要的,因此您可以将其与多种类型和算法一起使用。
设计一个好的哈希表需要对将使用它的上下文有充分的了解。应该使用开放式寻址还是链接链接?调整大小之前应接受什么级别的负载?应该使用昂贵的散列来避免冲突,还是使用粗糙而又快速的散列?
由于STL无法预测哪个是您的应用程序的最佳选择,因此默认值需要更灵活。树木“可以正常工作”,并且可以很好地缩放。
(C ++ 11确实使用添加了哈希表unordered_map
。您可以从文档中看到它需要设置策略来配置许多这些选项。)
那其他树呢?
与BST不同,红黑树提供快速查找并具有自我平衡能力。另一位用户指出了它比自平衡AVL树的优势。
Alexander Stepanov(STL的创建者)说,如果他再写std::map
一次,他将使用B *树而不是红黑树,因为它对于现代内存缓存更加友好。
此后最大的变化之一就是缓存的增长。高速缓存未命中的代价非常高,因此现在引用的位置更加重要。具有低引用局部性的基于节点的数据结构意义不大。如果我今天要设计STL,那我会有不同的容器集。例如,对于实现关联容器,内存中的B *树比红黑树好得多。- 亚历山大·斯捷潘诺夫(Alexander Stepanov)
地图应始终使用树吗?
另一个可能的地图实现是排序向量(插入排序)和二进制搜索。这对于不经常修改但经常被查询的容器来说是很好的选择。我经常在C语言中这样做,qsort
并且bsearch
是内置的。
我什至需要使用地图吗?
缓存注意事项意味着,即使在学校里我们曾经教过的情况(例如从列表中间删除一个元素),使用std::list
或std::deque
结束缓存也几乎没有意义std:vector
。应用相同的推理,使用for循环对列表进行线性搜索通常比为几个查询构建地图更有效和更清洁。
当然,选择可读的容器通常比性能更重要。
2017-06-14更新:我发表评论后,webbertiger编辑了答案。我应该指出,对我来说,它的答案现在好得多。但是我保留了我的答案,就像其他信息一样。
由于以下事实,我认为第一个答案是错误的(更正:不再同时存在),第三个答案的确认是错误的。我觉得我必须澄清一些事情...
最受欢迎的2种树是AVL和Red Black(RB)。主要区别在于利用率:
主要区别来自着色。RB树中的重新平衡操作确实比AVL少,因为着色使您有时可以跳过或缩短具有较高成本的重新平衡操作。由于颜色的原因,RB树还具有更高级别的节点,因为它可以接受黑色节点之间的红色节点(可能会有2倍以上的级别),从而使搜索(读取)的效率有所降低……但是,因为它是一种常数(2x),则保持在O(log n)中。
如果您考虑一棵树的修改对性能的影响(显着)与对一棵树的咨询的性能影响(几乎无关紧要),那么在一般情况下,相对于AVL而言,更倾向于RB。