红黑树

时间:2008-08-21 18:37:16

标签: algorithm binary-tree red-black-tree

我见过我最近读过的几本书中提到的二叉树和二进制搜索,但是由于我还在学习计算机科学,我还没有上过真正处理过的课程。算法和数据结构严重。

我查看了典型的来源(维基百科,谷歌),大多数关于(特别是)红黑树的实用性和实施​​的描述都是密集且难以理解的。我确信对于具有必要背景的人来说,它是完全合理的,但此刻它几乎就像一本外语。

那么是什么让二进制树在编程时发现的一些常见任务中有用呢?除此之外,您更喜欢使用哪些树(请包括示例实现)以及为什么?

12 个答案:

答案 0 :(得分:54)

红黑树有助于创造均衡的树木。二叉搜索树的主要问题是你可以很容易地使它们失衡。想象一下你的第一个数字是15.那么之后的所有数字都会小于15.你的左侧树很重,右侧没有任何东西。

红色黑树通过在插入或删除时强制平衡树来解决问题。它通过祖先节点和子节点之间的一系列旋转来实现这一点。该算法实际上非常简单,虽然它有点长。我建议拿起CLRS(Cormen,Lieserson,Rivest和Stein)教科书,“算法导论”和阅读RB树。

实施也不是那么短暂,所以在这里包含它可能并不是最好的。然而,对于需要访问大量数据的高性能应用程序,广泛使用树。它们提供了一种非常有效的查找节点的方法,插入/删除开销相对较小。同样,我建议看一下CLRS来了解它们是如何被使用的。

虽然可能没有明确使用BST,但几乎每一个现代RDBMS中都有一个使用树的例子。同样,您的文件系统几乎可以肯定地表示为某种树结构,并且文件同样以这种方式索引。树木为Google提供动力树木几乎可以为互联网上的每个网站提供动力。

答案 1 :(得分:18)

我只想解决这个问题“那么是什么让二进制树在你编程时遇到的一些常见任务中有用?”

这是一个很多人不同意的大话题。有人说,在CS程度上教授的算法,如二元搜索树和有向图,不会用于日常编程,因此无关紧要。其他人不同意,他们说这些算法和数据结构是我们所有编程的基础,即使你不必为自己编写一个,也必须理解它们。这会过滤关于良好面试和招聘实践的对话。例如,Steve Yeggeinterviewing at Google上有一篇解决此问题的文章。记住这场辩论;经验丰富的人不同意。

在典型的商业编程中,您可能根本不需要经常创建二叉树甚至树。但是,您将使用许多使用树在内部操作的类。每种语言中的许多核心组织类都使用树和哈希来存储和访问数据。

如果您参与了更多高性能的工作或情况,这些工作或情况有点超出了商业编程的规范,您会发现树木是直接的朋友。正如另一张海报所说,树是各种数据库和索引的核心数据结构。它们在数据挖掘和可视化,高级图形(2d和3d)以及许多其他计算问题中非常有用。

我在3d图形中使用了BSP (binary space partitioning) trees形式的二叉树。我目前正在再次查看树木,以便对Flash / Flex应用程序中的信息可视化进行大量地理编码数据和其他数据的排序。无论何时推动硬件的边界,或者想要在较低的硬件规格上运行,理解和选择最佳算法都可以在失败和成功之间产生差异。

答案 2 :(得分:10)

没有一个答案提到BST的确有什么好处。

如果您想要做的只是按值查找,那么散列表要快得多,O(1)插入和查找(分摊最佳情况)。

BST将是O(log N)查找,其中N是树中的节点数,插入也是O(log N)。

RB和AVL树很重要,因为这个属性提到了另一个答案,如果使用有序值创建普通BST,那么树将与插入的值的数量一样高,这对查找性能不利。

RB和AVL树之间的区别在于插入或删除后重新平衡所需的旋转,AVL树是重新平衡的O(log N),而RB树是O(1)。这种常量复杂性的好处的一个例子是,如果您需要跟踪回滚更改,则需要跟踪对回滚的更改,您必须使用AVL树跟踪O(log N)可能的更改。< / p>

为什么你愿意为哈希表支付树的费用?订购!哈希表没有顺序,另一方面,BST总是凭借其结构自然排序。因此,如果您发现自己将大量数据放入数组或其他容器中,然后再对其进行排序,那么BST可能是更好的解决方案。

树的order属性为您提供了许多有序的迭代功能,按顺序,深度优先,广度优先,预订,后订购。如果您想查找它们,这些迭代算法在不同情况下非常有用。

红色黑树几乎在每个有序的语言库容器中使用,C ++ Set and Map,.NET SortedDictionary,Java TreeSet等......

所以树木非常有用,你可以经常使用它们而不知道它。你很可能永远不会需要自己编写一个,但我强烈推荐它作为一个有趣的编程练习。

答案 3 :(得分:4)

红黑树和B树用于各种持久存储;因为树木是平衡的,所以可以减轻宽度和深度遍历的表现。

几乎所有现代数据库系统都使用树来存储数据。

答案 4 :(得分:2)

正如迈克尔所说,BST让世界变得圆满。如果您正在寻找一个好的树来实现,请查看AVL trees(维基百科)。它们具有平衡条件,因此它们保证为O(logn)。这种搜索效率使得进入任何一种索引过程都是合乎逻辑的。唯一能提高效率的是散列函数,但这些函数快速,快速,匆忙。此外,你遇到了Birthday Paradox(也称为鸽子洞问题)。

你在用什么教科书?我们使用了Mark Allen Weiss的Data Structures and Analysis in Java。我打算把它打开,因为我打字了。它有一个关于红黑树的很棒的部分,甚至包括实现它所讨论的所有树所需的代码。

答案 5 :(得分:2)

我见过的红黑树的最佳描述是Cormen,Leisersen和Rivest的“算法导论”。我甚至可以理解它足以部分实现一个(仅插入)。在各种网页上还有相当多的小程序,例如This One,可以为流程设置动画,并允许您观察并逐步构建构建树结构的算法的图形表示。

答案 6 :(得分:2)

红黑树保持平衡,因此您不必深入穿越以获取物品。保存的时间使得RB树O(log()n))处于WORST情况下,而不幸的二叉树可能会进入垂直配置并导致O(n)中的检索不良情况。这确实发生在实践中或随机数据上。因此,如果您需要时间关键代码(数据库检索,网络服务器等),您可以使用RB树来支持有序或无序列表/集。

但RBTrees适合新手!如果您正在进行AI并且需要执行搜索,那么您会发现很多状态信息。您可以使用持久的红黑在O(log(n))中分叉新状态。持久的红黑树在形态操作(插入/删除)之前和之后保留树的副本,但不复制整个树(通常和O(log(n))操作)。我为java开源了一个持久的红黑树

http://edinburghhacklab.com/2011/07/a-java-implementation-of-persistent-red-black-trees-open-sourced/

答案 7 :(得分:1)

由于您询问人们使用哪种树,您需要知道红黑树基本上是2-3-4 B树(即4阶B树)。 B树等同于二叉树(在您的问题中提到)。

Here是描述初始抽象的优秀资源,称为对称二进制B树,后来演变为RBTree。在有意义之前,您需要很好地掌握B树。总结一下:红黑树上的“红色”链接是表示属于B树节点的节点(键范围内的值)的一种方式,而“黑色”链接是在B中垂直连接的节点。 -tree。

所以,这就是你用B树翻译红黑树的规则时得到的结果(我使用格式红黑树规则 =&gt; B树等价物):

1)节点为红色或黑色。 =&GT; b树中的节点可以是节点的一部分,也可以是新级别的节点。

2)根是黑色的。 (有时省略此规则,因为它不影响分析)=&gt;根节点可以被视为内部根节点的一部分,作为虚构父节点的子节点。

3)所有叶子(NIL)都是黑色的。 (所有叶子的颜色与根相同。)=&gt;由于表示RB树的一种方法是省略叶子,我们可以将其排除在外。

4)每个红色节点的两个孩子都是黑色的。 =&GT; B树中内部节点的子节点总是位于另一个级别上。

5)从给定节点到其任何后代叶子的每个简单路径都包含相同数量的黑色节点。 =&GT; B树保持平衡,因为它要求所有叶节点处于相同的深度(因此B树节点的高度由从红黑树的根到叶的黑色链接的数量表示)< / p>

此外,Robert Sedgewick here还有一个更简单的“非标准”实现:(他是“em>算法与Wayne一起出版的书”的作者)

答案 8 :(得分:1)

这里有很多很多热量,但光线不多,所以让我们来看看是否可以提供一些。

首先,RB树是一种关联数据结构,不像一个数组,它不能取一个键并返回一个相关的值,除非它是0中的整数“键”连续整数的稀疏索引%。一个数组的大小也不能增长(是的,我也知道realloc(),但需要一个新的数组,然后是一个memcpy()),所以如果你有这些要求,数组将不会。阵列的内存效率是完美的。零浪费,但不是很聪明,或灵活 - realloc()不能承受。

第二,与元素数组上的bsearch()形成对比,这是一个关联数据结构,RB树可以动态地增大(缩小)自身的大小。 bsearch()适用于索引已知大小的数据结构,该结构将保持该大小。因此,如果您事先不知道数据的大小,或者需要添加或删除新元素,则会出现bsearch()。 Bsearch()和qsort()在经典C中都得到了很好的支持,并且具有良好的内存效率,但对于许多应用程序来说都不够动态。它们是我个人最喜欢的,因为它们快速,简单,如果你不处理实时应用程序,通常足够灵活。此外,在C / C ++中,您可以对指向数据记录的指针数组进行排序,指向struc {}成员,例如,您希望进行比较,然后重新排列指针数组中的指针,以便按顺序读取指针在指针排序的末尾,按排序顺序生成数据。将此与内存映射数据文件一起使用可以实现内存高效,快速且相当简单。您需要做的就是在比较功能中添加几个“*”。

第三,与哈希表相比,哈希表也必须是固定大小,并且一旦填充就无法增长,RB树将自动增长并平衡自身以维持其O(log( n))性能保证。特别是如果RB树的键是int,它可能比散列更快,因为即使散列表的复杂度是O(1),该1也可能是非常昂贵的散列计算。树的多个1时钟整数比较通常优于100时钟+散列计算,更不用说重新散列,而malloc()空间用于散列冲突和重新散列。最后,如果您想要ISAM访问以及对数据的密钥访问,则排除散列,因为散列表中固有的数据没有排序,这与任何树实现中的数据的自然排序形成对比。散列表的经典用法是为编译器提供对保留字表的键控访问。它的内存效率非常好。

第四,并且在任何列表中都非常低,是链接或双向链接列表,与数组相比,它自然支持元素插入和删除,并且这意味着,调整大小。它是所有数据结构中最慢的,因为每个元素只知道如何到达下一个元素,因此您必须平均搜索(element_knt / 2)链接以查找您的数据。它主要用于列表中间某处的插入和删除是常见的,尤其是列表是循环的并且提供昂贵的过程,这使得读取链接的时间相对较小。我的一般RX是使用任意大的数组而不是链表,如果你唯一的要求是它能够增加大小。如果你的数组不合适,你可以realloc()一个更大的数组。当您使用向量时,STL会为您“在幕后”执行此操作。原油,但如果您不需要插入,删除或键控查找,可能会快1,000倍。它的内存效率很差,特别是对于双链表。事实上,一个需要两个指针的双向链表就像红黑树一样具有内存效率低,而且没有其吸引人的快速有序检索特性。

第五,树支持对其排序数据的许多额外操作,而不是任何其他数据结构。例如,许多数据库查询利用这样的事实:通过指定它们的公共父级可以轻松指定一系列叶值,然后将后续处理集中在父级“拥有”的树的一部分上。这种方法提供的多线程潜力应该是显而易见的,因为只需要锁定树的一小部分区域 - 即,只有父级拥有的节点和父级本身。

简而言之,树木是数据结构的凯迪拉克。您在使用的内存方面付出了高昂的代价,但您可以获得完全自我维护的数据结构。这就是为什么,正如其他回复中所指出的,交易数据库几乎只使用树。

答案 9 :(得分:0)

如果您想看看红黑树应该如何以图形方式显示,我已经编写了一个红黑树的实现,您可以download here

答案 10 :(得分:0)

IME,几乎没有人理解RB树算法。人们可以将规则重复给您,但他们不理解为什么这些规则以及它们来自哪里。我也不例外: - )

出于这个原因,我更喜欢AVL算法,因为它很容易理解。一旦你理解了它,你就可以从头开始编码,因为它对你有意义。

答案 11 :(得分:0)

树木很快。如果在平衡二叉树中有一百万个节点,则平均需要进行二十次比较才能找到任何一个项目。如果链接列表中有一百万个节点,则平均需要五十万个比较才能找到相同的项目。

如果树不平衡,它可能和列表一样慢,也需要更多内存来存储。想象一棵树,大多数节点都有一个正确的孩子,但没有左孩子;它一个列表,但是如果一个显示,你仍然需要保留内存空间以放入左边的节点。

无论如何,AVL tree是第一个平衡二叉树算法,维基百科的文章非常清楚。关于红黑树的维基百科文章显然是泥巴,老实说。

除了二叉树之外,B树是树,其中每个节点可以有许多值。 B-Tree 不是二叉树,恰好是它的名字。它们对于有效利用内存非常有用;树的每个节点都可以调整大小以适应一个内存块,这样你就不会(慢慢地)去寻找内存中被分页到磁盘的大量不同内容。这是B-Tree的一个非凡例子。