红黑树

来源:互联网 发布:时时彩做计划软件 编辑:程序博客网 时间:2024/05/17 18:48

红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。
它是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。
红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。

  • 性质
    红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
    性质1. 节点是红色或黑色。
    性质2. 根节点是黑色。
    性质3 每个叶节点(NIL节点,空节点)是黑色的。
    性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
    性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
    这里写图片描述

上述性质倒是了说红黑树从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。我是没能理解为什么,不过想了下好像还真是这回事。我一直在想黑色节点是否可以连续,如果一个黑色节点下面一个红一个黑那么到这两叶子的黑色节点数就不同的,所以必须红色的节点下面有黑色的节点,这样才保持平衡。

一棵拥有n个内部结点的红黑树的树高h<=2log(n+1)(我就记一下吧。。。)


  • 术语

红黑树是一种特定类型的二叉树,它是在计算机科学中用来组织数据比如数字的块的一种结构。所有数据块都存储在节点中。这些节点中的某一个节点总是担当起始位置的功能,它不是任何节点的儿子,我们称之为根节点或根。它有最多两个”儿子”,都是它连接到的其他节点。所有这些儿子都可以有自己的儿子,以此类推。这样根节点就有了把它连接到在树中任何其他节点的路径。
如果一个节点没有儿子,我们称之为叶子节点,因为在直觉上它是在树的边缘上。子树是从特定节点可以延伸到的树的某一部分,其自身被当作一个树。在红黑树中,叶子被假定为 null 或空。
由于红黑树也是二叉查找树,它们当中每一个节点的比较值都必须大于或等于在它的左子树中的所有节点,并且小于或等于在它的右子树中的所有节点。这确保红黑树运作时能够快速的在树中查找给定的值。

  • 旋转

旋转的目的是将节点多的一支出让节点给另一个节点少的一支,旋转操作在插入和删除操作中经常会用到的。

下面盗个图吧。。。。

这里写图片描述
这里写图片描述

  • 插入与删除
  • -

http://www.cnblogs.com/longdouhzt/archive/2011/07/20/2112086.html
Okasaki插入方法
  首先用与二叉搜索树一样的方法将一个结点插入到红黑树中,并且颜色为红色。(这个新结点的子结点将是叶结点,根据定义,这些叶结点是黑色的。)此时,我们将或者破坏了性质2(根结点为黑色)或者破坏了性质4(不能有两个相邻的红色结点)。
  如果新插入的结点是根结点的话(这意味着在插入前该红黑树是空的),我们仅仅将这个结点的颜色改为黑色,插入操作就完成了。
   如果性质4遭到了破坏,这一定是由于新插入结点的父结点也是红色造成的。由于红黑树的根结点必须是黑色的,因此新插入的结点一定会存在一个祖父结点,并 且根据性质4这个祖父结点必然是黑色的。此时,由新插入结点的祖父结点为根的子树的结构一共有四种可能性(译者,前面这句话我没有看明白原文,我是用我的 理解写出来的,如果有误请指正。),如下面的图解所示。在Okasaki插入方法中,每一种可能出现的子树都被转换为图解正中间的那种子树形式。
  
  这里写图片描述
  (A,B,C与D表示任意的子树。我们曾经说过新插入结点的子结点一定是叶结点,但很快我们就会看到上面的图解适用于更普遍的情况)。
  首先,请注意在变换的过程中   另外,注意该变换不会改变从这颗子树的父结点到这颗子树中任何一个叶结点的路径中黑色结点的数量(当然前提是这颗子树有父结点)。我们再一次遇到了这样 的情形:即该红黑树只有可能违反性质2(如果y是根结点)或性质4(如果y的父结点是红色的),但这次变换带来了一个好处,即我们现在距离红黑树的根结点 靠近了两步。我们可以重复这种操作直到:或者y的父结点为黑色,在这种情况下插入操作完成;或者y成为根结点,在此情况下我们将y染为黑色后插入操作完 成。(将根结点染为黑色会对每条从根结点到叶结点的路径增加相同数量的黑色结点,因此如果在染色操作之前性质5没有遭到破坏那么操作之后也不会。)
  上述步骤保持了红黑树的性质,并且所花的时间与树高是成比例的,也即O(log n)。
  
  
  
  很显然,在旋转操作中的顺序保持不变。因此,如果操作前该树是一颗二叉搜索树,而且结构调整时只使用了旋转操作,那么调整后该树仍然是一颗二叉搜索树。在本文的余下部分,我们将仅仅使用旋转操作对树进行调整,因此我们无须再言明关于如何保持树中元素的正确排序问题。
  在下面的图解中,Okasaki插入方法中的变换操作被表示为一个或者两个旋转操作。
  这里写图片描述
  
  CLRS插入方法
  CLRS中给出了一种比Okasaki插入方法更复杂但效率稍高的插入方法。它的时间复杂度仍然是O(log n),但在大O中的常数要更小一些。
   CLRS插入方法与Okasaki插入方法一样都是从标准的二叉搜索树插入操作开始的,并且将这个新插入的结点染为红色,它们的区别在于如何处理遭到破 坏的性质4(不能存在两个相邻的红色结点)。我们要根据下端红色结点的叔叔结点的颜色区分两种情况。(下端红色结点是指在一对儿红色父结点/红色子结点中 的那个子结点。)让我们先考虑叔叔结点为黑色的情况。根据每个红色结点是其父结点的左子结点还是右子结点,这种情况可以分为四种子情况。下面的图解展示了 如何调整红黑树以及如何重新染色。
  这里写图片描述
  
   在这里我们感兴趣的是上面图解中的方法与Okasaki方法的比较。它们有两点不同。第一点是关于如何对最终的子树(图解中间的那个子树)进行染色的。 在Okasaki方法中,这颗子树的根结点y被染成红色而它的子结点被染成黑色,然而在CLRS方法中y被染成了黑色而它的子结点被染成了红色。将y染成 黑色意味着红黑树性质4(不能存在两个相邻的红色结点)不会在y这一点遭到破坏,因此对树的调整不需要向根结点的方向继续进行下去。在此情况下,CLRS 插入方法最多需要进行两次旋转操作即可完成插入。
  第二点不同是在这种情况下CLRS方法必须满足一个先决条件,即下端红色结点的叔叔结点必 须是黑色的。在上面的图解中我们可以很清楚地看出,如果那个叔叔结点(即子树A或者D的根结点)是红色的,那么最终的树中将存在两个相邻的红色结点,因此 这种方法不能适用于叔叔结点为红色的情况。
  下面我们考虑下端红色结点的叔叔结点为红色的情况。在这种情况下我们将上端红色结点和它的兄弟结 点(即下端红色结点的叔叔结点)染为黑色并且将它们的父结点染为红色。树的结构并没有进行调整。这时根据下端红色结点是其父结点的左子结点还是右子结点以 及上端红色结点是其父结点的左子结点还是右子结点可以分出四种情况,但是这四种情况从本质上来说都是相同的。下面图解只描述了一种情况:
  这里写图片描述
  
   很容易看出,在这种操作的过程中从树的根结点到叶结点的路径中的黑色结点数量没有发生变化。在此操作之后,红黑数的性质只有可能在该子树的根结点同时也 是整个树的根结点或者该子树的父结点是红色的情况下才会遭到破坏。换句话说,我们又将开始重复上述操作,但我们距离树的根结点又靠近了两步。照这样不断重 复该步骤直到:或者(i)z的父结点为黑色,此时插入操作结束;(ii)z成为根结点,我们将它染为黑色之后插入操作结束;或者(iii)我们遇到了下端 红色结点的叔叔结点为黑色的情况,这时我们只要做一或两次旋转操作即可完成插入。在最坏的情况下,我们必须对新插入的结点到根结点的路径上的每个结点进行 染色操作,此时需要的操作数为O(log n)。
  删除
  为了从红黑树中删除一个结点,我们将从一颗标准二叉搜索树的删除操作开始(参见CLRS,第12章)。我们回顾一下标准二叉搜索树的删除操作的三种情况:
  1. 要删除的结点没有子结点。在这种情况下,我们直接将它删除就可以了。如果这个结点是根结点,那么这颗树将成为空树;否则,将它的父结点中相应的子结点指针赋值为NULL。
  2. 要删除的结点有一个子结点。与上面一样,直接将它删除。如果它是根结点,那么它的子结点变为根结点;否则,将它的父结点中相应的子结点指针赋值为被删除结点的子结点的指针。
   3. 要删除的结点有两个子结点。在这种情况下,我们先找到这个结点的后继结点(successor),也就是它的右子树中最小的那个结点。然后我们将这两个结 点中的数据元素互换,之后删除这个后继结点。由于这个后继结点不可能有左子结点,因此删除该后继结点的操作必然会落入上面两种情况之一。
  注意,在树中被删除的结点并不一定是那个最初包含要删除的数据项的那个结点。但出于重建红黑树性质的目的,我们只关心最终被删除的那个结点。我们称这个结点为v,并称它的父结点为p(v)。
  v的子结点中至少有一个为叶结点。如果v有一个非叶子结点,那么v在这颗树中的位置将被这个子结点取代;否则,它的位置将被一个叶结点取代。我们用u来表示二叉搜索树删除操作后在树中取代了v的位置的那个结点。如果u是叶结点,那么我们可以确定它是黑色的。
   如果v是红色的,那么删除操作就完成了---因为这种删除不会破坏红黑树的任何性质。所以,我们下面假定v是黑色的。删除了v之后,从根结点到v的所有 子孙叶结点的路径将会比树中其它的从根结点到叶结点的路径拥有更少的黑色结点,这会破坏红黑树的性质5。另外,如果p(v)与u都是红色的,那么性质4也 会遭到破坏。但实际上我们解决性质5遭到破坏的方案在不用作任何额外工作的情况下就可以同时解决性质4遭到破坏的问题,所以从现在开始我们将集中精力考虑 性质5的问题。
  让我们在头脑中给u打上一个黑色记号(black token)。这个记号表示从根结点到这个带记号结点的所有子孙叶结点的路径上都缺少一个黑色结点(在一开始,这是由于v被删除了)。我们会将这个记号一 直朝树的顶部移动直到性质5重新恢复。在下面的图解中用一个黑色的方块表示这个记号。如果带有这个记号的结点是黑色的,那么我们称之为双黑色结点 (doubly black node)。
  注意这个记号只是一个概念上的东西,在树的数据结构中并不存在物理实现。
  我们要区分四种不同的情况。
   A. 如果带记号的结点是红色的或者它是树的根结点(或两者皆是),只要将它染为黑色就可以完成删除操作。注意,这样就会恢复红黑树的性质4(不能存在两个相邻 的红色结点)。而且,性质5也会被恢复,因为这个记号表示从根结点到该结点的所有子孙叶结点的路径需要增加一个黑色结点以便使这些路径与其它的根结点到叶 结点路径所包含的黑色结点数量相同。通过将这个红色结点改变为黑色,我们就在这些缺少一个黑色结点的路径上添加了一个黑色结点。
  如果带记号的结点是根结点并且为黑色,那么直接将这个标记丢掉就可以了。在这种情况下,树中每条从根结点到叶结点的路径的黑色结点数量都比删除操作前少了一个,并且依旧保持住了性质5。
  在余下的情况里,我们可以假设这个带记号的结点是黑色的,并且不是根结点。
  B. 如果这个双黑色结点的兄弟结点以及两个侄子结点都是黑色的,那么我们就将它的兄弟结点染为红色之后将这个记号朝树根的方向移动一步。
   下面的图解展示了两种可能出现的子情况。环绕y的虚线表示在此并我们不关心y的颜色,而在A,B,C和D的上面的小圆圈表示这些子树的根结点是黑色的 (译者:注意这个双黑色结点必然会有两个非叶结点的侄子结点。这是因为这个双黑色结点的记号表示从根结点到该结点的所有子孙叶结点的路径中的黑色结点数量 都比其它的根结点到叶结点路径所包含的黑色结点数量少1,而该双黑色结点本身就是一个黑色结点,因此从它的兄弟结点到其子孙叶结点的路径上的黑色结点数量 必然要大于1,我们很容易看出如果其兄弟结点的任何一个子结点为叶结点的话这一点是不可能满足的,因此这个双黑色结点的必然会有两个非叶结点的侄子结 点)。
  
  这里写图片描述
   将那个兄弟结点染为红色,就会从所有到该结点的子孙叶结点的路径上去掉一个黑色结点,因此现在这些路径上的黑色结点数量与到双黑色结点的子孙叶结点的路 径上的黑色结点数量一致了。我们将这个记号向上移动到y,这表明现在所有到y的子孙叶结点的路径上缺少一个黑色结点。此时问题仍然没有得到解决,但我们又 向树根推进了一步。
  很显然,只有带记号的结点的两个侄子结点都是黑色时才能进行上述操作,这是因为如果有一个侄子结点是红色的那么该操作会导致出现两个相邻的红色结点。
  C. 如果带记号的结点的兄弟结点是红色的,那么我们就进行一次旋转操作并改变结点颜色。下面的图解展示了两种可能出现的情况:
  这里写图片描述
  
  注意上面的操作并不会改变从根结点到任何叶结点路径上的黑色结点数量,并且它确保了在操作之后这个双黑色结点的兄弟结点是黑色的,这使得后续的操作或者属于情况B,或者属于情况D。
   由于这个记号比起操作前离树的根结点更远了,所以看起来似乎我们向后倒退了。但请注意现在这个双黑色结点的父结点是红色的了,所以如果下一步操作属于情 况B,那么这个记号将会向上移动到那个红色结点,然后我们只要将它染为黑色就完成了。此外,下面将会展示,在情况D下,我们总是能够将这个记号消耗掉从而 完成删除操作。因此这种表面上的倒退现象实际上意味着删除操作就快要完成了。
  D. 最终,我们遇到了双黑色结点有一个黑色兄弟结点并至少一个侄子结点是红色的情况。我们下面给出一个结点x的近侄子结点(near nephew)的定义:如果x是其父结点的左子结点,那么x的兄弟结点的左子结点为x的近侄子结点,否则x的兄弟结点的右子结点为x的近侄子结点;而另一 个侄子结点则为x的远侄子结点(far nephew)。(在下面的图解中可以看出,x的近侄子结点要比它的远侄子结点距离x更近。)
  现在 我们会遇到两种子情况:(i)双黑色结点的远侄子结点是黑色的,在此情况下它的近侄子结点一定是红色的;(ii)远侄子结点是红色的,在此情况下它的近侄 子结点可以为任何颜色。如下面的图解所示,子情况(i)可以通过一次旋转和变色转换为子情况(ii),而在子情况(ii)下只要通过一次旋转和变色就可以 完成删除操作。根据双黑色结点是其父结点的左子结点还是右子结点,下面图解中的两行显示出两种对称的形式。
  这里写图片描述
  
  在这种情况下我们生成了一个额外的黑色结点,记号被丢掉,删除操作完成。从上面图解中很容易看出,所有到带记号结点的子孙叶结点的路径上的黑色结点数量增加了1,而其它的路径上的黑色结点数量保持不变。很显然,在此刻红黑树的任何性质都没有遭到破坏。
  将上面的所有情况综合起来,我们可以看出在最坏的情况下我们必须沿着从叶结点到根结点的路径每次都执行常量次数的操作,因此删除操作的时间复杂度为O(log n)。

原创粉丝点击