数据结构:红黑树解析

本文参考:Google、算法导论、STL源码剖析、计算机程序设计艺术。

推荐阅读

  1. Left-Leaning Red-Black Trees, Dagstuhl Workshop on Data Structures, Wadern, Germany, February, 2008,直接下载:http://www.cs.princeton.edu/~rs/talks/LLRB/RedBlack.pdf
  2. 本文的github优化版:https://github.com/julycoding/The-Art-Of-Programming-By-July/blob/master/ebook/zh/03.01.md

一、红黑树的介绍

先来看下算法导论对R-B Tree的介绍:

红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。

通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

红黑树,作为一棵二叉查找树,满足二叉查找树的一般性质。下面,来了解下 二叉查找树的一般性质。

二叉查找树

二叉查找树,也称有序二叉树(ordered binary tree),或已排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树:

  • 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 任意节点的左、右子树也分别为二叉查找树。
  • 没有键值相等的节点(no duplicate nodes)。

因为一棵由n个结点随机构造的二叉查找树的高度为lgn,所以顺理成章,二叉查找树的一般操作的执行时间为O(lgn)。但二叉查找树若退化成了一棵具有n个结点的线性链后,则这些操作最坏情况运行时间为O(n)。

红黑树虽然本质上是一棵二叉查找树,但它在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)。

但它是如何保证一棵n个结点的红黑树的高度始终保持在logn的呢?这就引出了红黑树的5个性质:

  1. 每个结点要么是红的要么是黑的。
  2. 根结点是黑的。
  3. 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。
  4. 如果一个结点是红的,那么它的两个儿子都是黑的。
  5. 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。

正是红黑树的这5条性质,使一棵n个结点的红黑树始终保持了logn的高度,从而也就解释了上面所说的“红黑树的查找、插入、删除的时间复杂度最坏为O(log n)”这一结论成立的原因。

(注:上述第3、5点性质中所说的NULL结点,包括wikipedia.算法导论上所认为的叶子结点即为树尾端的NIL指针,或者说NULL结点。然百度百科以及网上一些其它博文直接说的叶结点,则易引起误会,因,此叶结点非子结点)

如下图所示,即是一颗红黑树(下图引自wikipedia:http://t.cn/hgvH1l):

此图忽略了叶子和根部的父结点。同时,上文中我们所说的 "叶结点" 或"NULL结点",如上图所示,它不包含数据而只充当树在此结束的指示,这些节点在绘图中经常被省略,望看到此文后的读者朋友注意。

二、树的旋转知识

当在对红黑树进行插入和删除等操作时,对树做了修改可能会破坏红黑树的性质。为了继续保持红黑树的性质,可以通过对结点进行重新着色,以及对树进行相关的旋转操作,即通过修改树中某些结点的颜色及指针结构,来达到对红黑树进行插入或删除结点等操作后继续保持它的性质或平衡的目的。

树的旋转分为左旋和右旋,下面借助图来介绍一下左旋和右旋这两种操作。

1.左旋

如上图所示,当在某个结点pivot上,做左旋操作时,我们假设它的右孩子y不是NIL[T],pivot可以为任何不是NIL[T]的左子结点。左旋以pivot到Y之间的链为“支轴”进行,它使Y成为该子树的新根,而Y的左孩子b则成为pivot的右孩子。

[cpp] view
plain
 copy

print?

  1. LeftRoate(T, x)
  2. y ← x.right                    //定义y:y是x的右孩子
  3. x.right ← y.left                //y的左孩子成为x的右孩子
  4. if y.left ≠ T.nil
  5. y.left.p ← x
  6. y.p ← x.p                      //x的父结点成为y的父结点
  7. if x.p = T.nil
  8. then T.root ← y
  9. else if x = x.p.left
  10. then x.p.left ← y
  11. else x.p.right ← y
  12. y.left ← x                       //x作为y的左孩子
  13. x.p ← y

2.右旋

右旋与左旋差不多,再此不做详细介绍。

树在经过左旋右旋之后,树的搜索性质保持不变,但树的红黑性质则被破坏了,所以,红黑树插入和删除数据后,需要利用旋转与颜色重涂来重新恢复树的红黑性质。

至于有些书如《STL源码剖析》有对双旋的描述,其实双旋只是单旋的两次应用,并无新的内容,因此这里就不再介绍了,而且左右旋也是相互对称的,只要理解其中一种旋转就可以了。

三、红黑树的插入

要真正理解红黑树的插入,还得先理解二叉查找树的插入。磨刀不误砍柴工,咱们再来了解一下二叉查找树的插入和红黑树的插入。

如果要在二叉查找树中插入一个结点,首先要查找到结点要插入的位置,然后进行插入。假设插入的结点为z的话,插入的伪代码如下:

[cpp] view
plain
 copy

print?

  1. TREE-INSERT(T, z)
  2. y ← NIL
  3. x ← T.root
  4. while x ≠ NIL
  5. do y ←  x
  6. if z.key < x.key
  7. then x ← x.left
  8. else x ← x.right
  9. z.p ← y
  10. if y == NIL
  11. then T.root ← z
  12. else if z.key < y.key
  13. then y.left ← z
  14. else y.right ← z

红黑树的插入和插入修复

现在我们了解了二叉查找树的插入,接下来,咱们便来具体了解下红黑树的插入操作。红黑树的插入相当于在二叉查找树插入的基础上,为了重新恢复平衡,继续做了插入修复操作。

假设插入的结点为z,红黑树的插入伪代码具体如下所示:

[cpp] view
plain
 copy

print?

  1. RB-INSERT(T, z)
  2. y ← nil
  3. x ← T.root
  4. while x ≠ T.nil
  5. do y ← x
  6. if z.key < x.key
  7. then x ← x.left
  8. else x ← x.right
  9. z.p ← y
  10. if y == nil[T]
  11. then T.root ← z
  12. else if z.key < y.key
  13. then y.left ← z
  14. else y.right ← z
  15. z.left ← T.nil
  16. z.right ← T.nil
  17. z.color ← RED
  18. RB-INSERT-FIXUP(T, z)

把上面这段红黑树的插入代码,跟之前看到的二叉查找树的插入代码比较一下可以看出,RB-INSERT(T, z)前面的第1~13行代码基本上就是二叉查找树的插入代码,然后第14~16行代码把z的左孩子和右孩子都赋为叶结点nil,再把z结点着为红色,最后为保证红黑性质在插入操作后依然保持,调用一个辅助程序RB-INSERT-FIXUP来对结点进行重新着色,并旋转。

换言之,如果插入的是根结点,由于原树是空树,此情况只会违反性质2,因此直接把此结点涂为黑色;如果插入的结点的父结点是黑色,由于此不会违反性质2和性质4,红黑树没有被破坏,所以此时什么也不做。

但当遇到下述3种情况时又该如何调整呢?

  • ● 插入修复情况1:如果当前结点的父结点是红色且祖父结点的另一个子结点(叔叔结点)是红色

    ● 插入修复情况2:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子

    ● 插入修复情况3:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左子

答案就是根据红黑树插入代码RB-INSERT(T, z)最后一行调用的RB-INSERT-FIXUP(T, z)函数所示的步骤进行操作,具体如下所示:

[cpp] view
plain
 copy

print?

  1. RB-INSERT-FIXUP(T, z)
  2. while z.p.color == RED
  3. do if z.p == z.p.p.left
  4. then y ← z.p.p.right
  5. if y.color == RED
  6. then z.p.color ← BLACK               ? Case 1
  7. y.color ← BLACK                    ? Case 1
  8. z.p.p.color ← RED                    ? Case 1
  9. z ← z.p.p                            ? Case 1
  10. else if z == z.p.right
  11. then z ← z.p                          ? Case 2
  12. LEFT-ROTATE(T, z)                   ? Case 2
  13. z.p.color ← BLACK                        ? Case 3
  14. z.p.p.color ← RED                         ? Case 3
  15. RIGHT-ROTATE(T, z.p.p)                  ? Case 3
  16. else (same as then clause with "right" and "left" exchanged)
  17. T.root.color ← BLACK

下面,咱们来分别处理上述3种插入修复情况。

  • 插入修复情况1:当前结点的父结点是红色,祖父结点的另一个子结点(叔叔结点)是红色。

如下代码所示:

[cpp] view
plain
 copy

print?

  1. while z.p.color == RED
  2. do if z.p == z.p.p.left
  3. then y ← z.p.p.right
  4. if y.color == RED

此时父结点的父结点一定存在,否则插入前就已不是红黑树。与此同时,又分为父结点是祖父结点的左孩子还是右孩子,根据对称性,我们只要解开一个方向就可以了。这里只考虑父结点为祖父左孩子的情况,如下图所示。

对此,我们的解决策略是:将当前节点的父节点和叔叔节点涂黑,祖父结点涂红,把当前结点指向祖父节点,从新的当前节点重新开始算法。即如下代码所示:

[cpp] view
plain
 copy

print?

  1. then z.p.color ← BLACK               ? Case 1
  2. y.color ← BLACK                    ? Case 1
  3. z.p.p.color ← RED                    ? Case 1
  4. z ← z.p.p                            ? Case 1

所以,变化后如下图所示:

于是,插入修复情况1转换成了插入修复情况2。

  • 插入修复情况2:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子

此时,解决对策是:当前节点的父节点做为新的当前节点,以新当前节点为支点左旋。即如下代码所示:

[cpp] view
plain
 copy

print?

  1. else if z == z.p.right
  2. then z ← z.p                          ? Case 2
  3. LEFT-ROTATE(T, z)                   ? Case 2

所以红黑树由之前的:

变化成:

从而插入修复情况2转换成了插入修复情况3。

  • 插入修复情况3:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左孩子

解决对策是:父节点变为黑色,祖父节点变为红色,在祖父节点为支点右旋,操作代码为:

[cpp] view
plain
 copy

print?

  1. z.p.color ← BLACK                        ? Case 3
  2. z.p.p.color ← RED                         ? Case 3
  3. RIGHT-ROTATE(T, z.p.p)                  ? Case 3

最后,把根结点涂为黑色,整棵红黑树便重新恢复了平衡。所以红黑树由之前的:

变化成:

「回顾:经过上面情况3、情况4、情况5等3种插入修复情况的操作示意图,读者自会发现,后面的情况4、情况5都是针对情况3插入节点4以后,进行的一系列插入修复情况操作,不过,指向当前节点N指针一直在变化。所以,你可以想当然的认为:整个下来,情况3、4、5就是一个完整的插入修复情况的操作流程」

四、红黑树的删除

接下来,咱们最后来了解,红黑树的删除操作。

"我们删除的节点的方法与常规二叉搜索树中删除节点的方法是一样的,如果被删除的节点不是有双非空子女,则直接删除这个节点,用它的唯一子节点顶替它的位置,如果它的子节点分是空节点,那就用空节点顶替它的位置,如果它的双子全为非空,我们就把它的直接后继节点内容复制到它的位置,之后以同样的方式删除它的后继节点,它的后继节点不可能是双子非空,因此此传递过程最多只进行一次。”

二叉查找树的删除

继续讲解之前,补充说明下二叉树结点删除的几种情况,待删除的节点按照儿子的个数可以分为三种:

  1. 没有儿子,即为叶结点。直接把父结点的对应儿子指针设为NULL,删除儿子结点就OK了。
  2. 只有一个儿子。那么把父结点的相应儿子指针指向儿子的独生子,删除儿子结点也OK了。
  3. 有两个儿子。这是最麻烦的情况,因为你删除节点之后,还要保证满足搜索二叉树的结构。其实也比较容易,我们可以选择左儿子中的最大元素或者右儿子中的最小元素放到待删除节点的位置,就可以保证结构的不变。当然,你要记得调整子树,毕竟又出现了节点删除。习惯上大家选择左儿子中的最大元素,其实选择右儿子的最小元素也一样,没有任何差别,只是人们习惯从左向右。这里咱们也选择左儿子的最大元素,将它放到待删结点的位置。左儿子的最大元素其实很好找,只要顺着左儿子不断的去搜索右子树就可以了,直到找到一个没有右子树的结点。那就是最大的了。

二叉查找树的删除代码如下所示:

[cpp] view
plain
 copy

print?

  1. TREE-DELETE(T, z)
  2. 1  if left[z] = NIL or right[z] = NIL
  3. 2      then y ← z
  4. 3      else y ← TREE-SUCCESSOR(z)
  5. 4  if left[y] ≠ NIL
  6. 5      then x ← left[y]
  7. 6      else x ← right[y]
  8. 7  if x ≠ NIL
  9. 8      then p[x] ← p[y]
  10. 9  if p[y] = NIL
  11. 10      then root[T] ← x
  12. 11      else if y = left[p[y]]
  13. 12              then left[p[y]] ← x
  14. 13              else right[p[y]] ← x
  15. 14  if y ≠ z
  16. 15      then key[z] ← key[y]
  17. 16           copy y‘s satellite data into z
  18. 17  return y

红黑树的删除和删除修复

OK,回到红黑树上来,红黑树结点删除的算法实现是:

RB-DELETE(T, z) 单纯删除结点的总操作

[cpp] view
plain
 copy

print?

  1. 1 if left[z] = nil[T] or right[z] = nil[T]
  2. 2    then y ← z
  3. 3    else y ← TREE-SUCCESSOR(z)
  4. 4 if left[y] ≠ nil[T]
  5. 5    then x ← left[y]
  6. 6    else x ← right[y]
  7. 7 p[x] ← p[y]
  8. 8 if p[y] = nil[T]
  9. 9    then root[T] ← x
  10. 10    else if y = left[p[y]]
  11. 11            then left[p[y]] ← x
  12. 12            else right[p[y]] ← x
  13. 13 if y ≠ z
  14. 14    then key[z] ← key[y]
  15. 15         copy y‘s satellite data into z
  16. 16 if color[y] = BLACK
  17. 17    then RB-DELETE-FIXUP(T, x)
  18. 18 return y

“在删除节点后,原红黑树的性质可能被改变,如果删除的是红色节点,那么原红黑树的性质依旧保持,此时不用做修正操作,如果删除的节点是黑色节点,原红黑树的性质可能会被改变,我们要对其做修正操作。那么哪些树的性质会发生变化呢,如果删除节点不是树唯一节点,那么删除节点的那一个支的到各叶节点的黑色节点数会发生变化,此时性质5被破坏。如果被删节点的唯一非空子节点是红色,而被删节点的父节点也是红色,那么性质4被破坏。如果被删节点是根节点,而它的唯一非空子节点是红色,则删除后新根节点将变成红色,违背性质2。”

RB-DELETE-FIXUP(T, x) 恢复与保持红黑性质的工作

[cpp] view
plain
 copy

print?

  1. 1 while x ≠ root[T] and color[x] = BLACK
  2. 2     do if x = left[p[x]]
  3. 3           then w ← right[p[x]]
  4. 4                if color[w] = RED
  5. 5                   then color[w] ← BLACK                        ?  Case 1
  6. 6                        color[p[x]] ← RED                       ?  Case 1
  7. 7                        LEFT-ROTATE(T, p[x])                    ?  Case 1
  8. 8                        w ← right[p[x]]                         ?  Case 1
  9. 9                if color[left[w]] = BLACK and color[right[w]] = BLACK
  10. 10                   then color[w] ← RED                          ?  Case 2
  11. 11                        x ← p[x]                                ?  Case 2
  12. 12                   else if color[right[w]] = BLACK
  13. 13                           then color[left[w]] ← BLACK          ?  Case 3
  14. 14                                color[w] ← RED                  ?  Case 3
  15. 15                                RIGHT-ROTATE(T, w)              ?  Case 3
  16. 16                                w ← right[p[x]]                 ?  Case 3
  17. 17                         color[w] ← color[p[x]]                 ?  Case 4
  18. 18                         color[p[x]] ← BLACK                    ?  Case 4
  19. 19                         color[right[w]] ← BLACK                ?  Case 4
  20. 20                         LEFT-ROTATE(T, p[x])                   ?  Case 4
  21. 21                         x ← root[T]                            ?  Case 4
  22. 22        else (same as then clause with "right" and "left" exchanged)
  23. 23 color[x] ← BLACK

“上面的修复情况看起来有些复杂,下面我们用一个分析技巧:我们从被删节点后来顶替它的那个节点开始调整,并认为它有额外的一重黑色。这里额外一重黑色是什么意思呢,我们不是把红黑树的节点加上除红与黑的另一种颜色,这里只是一种假设,我们认为我们当前指向它,因此空有额外一种黑色,可以认为它的黑色是从它的父节点被删除后继承给它的,它现在可以容纳两种颜色,如果它原来是红色,那么现在是红+黑,如果原来是黑色,那么它现在的颜色是黑+黑。有了这重额外的黑色,原红黑树性质5就能保持不变。现在只要恢复其它性质就可以了,做法还是尽量向根移动和穷举所有可能性。"--saturnman。

如果是以下情况,恢复比较简单:

  • a)当前节点是红+黑色

    解法,直接把当前节点染成黑色,结束此时红黑树性质全部恢复。

  • b)当前节点是黑+黑且是根节点, 解法:什么都不做,结束。

但如果是以下情况呢?:

  • 删除修复情况1:当前节点是黑+黑且兄弟节点为红色(此时父节点和兄弟节点的子节点分为黑)
  • 删除修复情况2:当前节点是黑加黑且兄弟是黑色且兄弟节点的两个子节点全为黑色
  • 删除修复情况3:当前节点颜色是黑+黑,兄弟节点是黑色,兄弟的左子是红色,右子是黑色
  • 删除修复情况4:当前节点颜色是黑-黑色,它的兄弟节点是黑色,但是兄弟节点的右子是红色,兄弟节点左子的颜色任意

此时,我们需要调用RB-DELETE-FIXUP(T, x),来恢复与保持红黑性质的工作。

下面,咱们便来分别处理这4种删除修复情况。

删除修复情况1:当前节点是黑+黑且兄弟节点为红色(此时父节点和兄弟节点的子节点分为黑)。

解法:把父节点染成红色,把兄弟结点染成黑色,之后重新进入算法(我们只讨论当前节点是其父节点左孩子时的情况)。此变换后原红黑树性质5不变,而把问题转化为兄弟节点为黑色的情况(注:变化前,原本就未违反性质5,只是为了把问题转化为兄弟节点为黑色的情况)。 即如下代码操作:

[cpp] view
plain
 copy

print?

  1. //调用RB-DELETE-FIXUP(T, x) 的1-8行代码
  2. 1 while x ≠ root[T] and color[x] = BLACK
  3. 2     do if x = left[p[x]]
  4. 3           then w ← right[p[x]]
  5. 4                if color[w] = RED
  6. 5                   then color[w] ← BLACK                        ?  Case 1
  7. 6                        color[p[x]] ← RED                       ?  Case 1
  8. 7                        LEFT-ROTATE(T, p[x])                    ?  Case 1
  9. 8                        w ← right[p[x]]                         ?  Case 1

变化前:

变化后:

删除修复情况2:当前节点是黑加黑且兄弟是黑色且兄弟节点的两个子节点全为黑色。

解法:把当前节点和兄弟节点中抽取一重黑色追加到父节点上,把父节点当成新的当前节点,重新进入算法。(此变换后性质5不变),即调用RB-INSERT-FIXUP(T, z) 的第9-10行代码操作,如下:

[cpp] view
plain
 copy

print?

  1. //调用RB-DELETE-FIXUP(T, x) 的9-11行代码
  2. 9                if color[left[w]] = BLACK and color[right[w]] = BLACK
  3. 10                   then color[w] ← RED                          ?  Case 2
  4. 11                        x p[x]                                  ?  Case 2

变化前

变化后

删除修复情况3:当前节点颜色是黑+黑,兄弟节点是黑色,兄弟的左子是红色,右子是黑色。

解法:把兄弟结点染红,兄弟左子节点染黑,之后再在兄弟节点为支点解右旋,之后重新进入算法。此是把当前的情况转化为情况4,而性质5得以保持,即调用RB-INSERT-FIXUP(T, z) 的第12-16行代码,如下所示:

[cpp] view
plain
 copy

print?

  1. //调用RB-DELETE-FIXUP(T, x) 的第12-16行代码
  2. 12                   else if color[right[w]] = BLACK
  3. 13                           then color[left[w]] ← BLACK          ?  Case 3
  4. 14                                color[w] ← RED                  ?  Case 3
  5. 15                                RIGHT-ROTATE(T, w)              ?  Case 3
  6. 16                                w ← right[p[x]]                 ?  Case 3

变化前:

变化后:

删除修复情况4:当前节点颜色是黑-黑色,它的兄弟节点是黑色,但是兄弟节点的右子是红色,兄弟节点左子的颜色任意。

解法:把兄弟节点染成当前节点父节点的颜色,把当前节点父节点染成黑色,兄弟节点右子染成黑色,之后以当前节点的父节点为支点进行左旋,此时算法结束,红黑树所有性质调整正确,即调用RB-INSERT-FIXUP(T, z)的第17-21行代码,如下所示:

[cpp] view
plain
 copy

print?

  1. //调用RB-DELETE-FIXUP(T, x) 的第17-21行代码
  2. 17                         color[w] ← color[p[x]]                 ?  Case 4
  3. 18                         color[p[x]] ← BLACK                    ?  Case 4
  4. 19                         color[right[w]] ← BLACK                ?  Case 4
  5. 20                         LEFT-ROTATE(T, p[x])                   ?  Case 4
  6. 21                         x ← root[T]                            ?  Case 4

变化前:

变化后:

最后值得一提的是上述删除修复的情况1~4都只是树的局部,并非树的整体全部,且删除修复情况3、4在经过上面的调整后,调整还没结束(还得继续调整直至重新恢复平衡,只是图并没有画出来)。

后面会继续修改完善下本文,感谢关注,thanks。

July、二零一四年九月十五日修订。

----------------

之前在学校寝室画红黑树画了好几个钟头,贴俩张图:

红黑树插入修复的3种情况:

红黑树删除修复的4种情况:



updated

继续请看本文的github优化版本:https://github.com/julycoding/The-Art-Of-Programming-By-July/blob/master/ebook/zh/03.01.md,或看看这个PPT:http://vdisk.weibo.com/s/zrFL6OXJNfNVU。另,对应新书《编程之法:面试和算法心得》3.1节。July、二零一四年五月三日。

时间: 2024-10-06 10:39:17

数据结构:红黑树解析的相关文章

马程序员学习笔记——红黑树解析四

---------------------- ASP.Net+Unity开发..Net培训.期待与您交流! ---------------------- 本篇是将上面三篇的理论知识转化成代码,java实现 首先,看一下算法导论里的伪代码 一.左旋 The pseudocode for LEFT-ROTATE assumes that right[x] ≠ nil[T] and that the root's parent is nil[T].(伪代码的左旋方法中假设X的右孩子不为空) LEFT-

马程序员学习笔记——红黑树解析二

---------------------- ASP.Net+Unity开发..Net培训.期待与您交流! ---------------------- 四.树中删除元素 1.先找到需要删除的元素. 2. 2.1如果被删元素没有子元素,那么直接用NIL节点代替他: 2.2如果被删元素只有一个子元素,那么直接用这个子元素代替他: 2.3如果被删元素有两个子元素,那么就用左子元素中的最大元素或者右子元素的最小元素代替他. 比如说原来要删除的元素是N,N有两个分支,其中P是N左分支中的最大元素,那么就

马程序员学习笔记——红黑树解析三

---------------------- ASP.Net+Unity开发..Net培训.期待与您交流! ---------------------- 六.树的前序.中序.后序 前序遍历(根左右): 1.访问根节点 2.前序遍历左子树 3.前序遍历右子树 中序遍历(左根右): 1.中序遍历左子树 2.访问根节点 3.中序遍历右子树 后序遍历(左右根): 1.后序遍历左子树 2.后序遍历右子树 3.访问根节点 扩展:已知前序.中序遍历,求后序遍历? 例: 前序遍历: GDAFEMHZ 中序遍历:

数据结构 - 红黑树(Red Black Tree)插入详解与实现(Java)

最终还是决定把红黑树的篇章一分为二,插入操作一篇,删除操作一篇,因为合在一起写篇幅实在太长了,写起来都觉得累,何况是阅读并理解的读者. 红黑树删除操作请参考 数据结构 - 红黑树(Red Black Tree)删除详解与实现(Java) 现在网络上最不缺的就是对某个知识点的讲解博文,各种花样标题百出,更有类似"一文讲懂xxx","史上最简单的xxx讲解","xxx看了还不懂你打我"之类云云.其中也不乏有些理论甚至是举例都雷同的两篇不同文章,至于作

数据结构-红黑树

转自:http://dongxicheng.org/structure/red-black-tree/ 1. 简介 红黑树是一种自平衡二叉查找树.它的统计性能要好于平衡二叉树(AVL树),因此,红黑树在很多地方都有应用.在C++ STL中,很多部分(目前包括set, multiset, map, multimap)应用了红黑树的变体(SGI STL中的红黑树有一些变化,这些修改提供了更好的性能,以及对set操作的支持).它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它

数据结构 - 红黑树学习

红黑树 红黑树算是用的比较多,但是平时自己很少写的一种数据结构了,先看下介绍: 红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组. 看~ 典型是用来实现关联数组,能想到什么数据结构呢?list map set这些容器的底层都是红黑树来实现的. 红黑树和AVL树(平衡二叉查找树) 红黑树并不是完全平衡的一棵树,所以红黑树是在平均时间(经验)上为O(log n)的复杂度,包括插入,删除和查找. 红黑树规则 所有节点都分为红色

最详细的红黑树解析

红黑树 红黑树(英语:Red–black tree)是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组.它是在1972年由鲁道夫·贝尔发明的,他称之为"对称二叉B树",它现代的名字是在Leo J. Guibas和Robert Sedgewick于1978年写的一篇论文中获得的.它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的:它可以在O(log n)时间内做查找,插入和删除,这里的n是树中元素的数目. 用途和好处 红黑树和AVL树

数据结构-红黑树详解

介绍: 红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组. 它是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees).后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的"红黑树". 红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能. 它虽然

数据结构——红黑树

红黑树是二叉排序树的改进, 红黑树有几个特点: 1:节点只有2中颜色,红色和黑色. 2:根节点一定是黑色节点. 3:红色节点的子节点一定是黑色节点. 4:黑色高度(根节点到每个叶子节点的路径长度包含相同的黑色节点)相等. 规定的插入的节点一定是红色节点, 红黑树的插入节点后需要调整的规则,插入节点需要调整的情况有3种: 情况1:插入的节点的父节点和叔叔节点都为红色: 以上情况节点4为插入节点(当前节点),这种情况调整方式是将父节点和叔叔节点都调整为黑色节点,祖父节点调整为红色,将祖父节点变为当前