数据结构中的树

来源:互联网 发布:php print r 编辑:程序博客网 时间:2024/06/07 05:31

声明:

尊重原创,转载请注明出处http://blog.csdn.net/lizo_is_me/article/details/44260025

1 平衡二叉树

平衡二叉树(Balanced binary tree)是由阿德尔森-维尔斯和兰迪斯(Adelson-Velskii and Landis)于1962年首先提出的,所以又称为AVL树。

定义:平衡二叉树或为空树,或为如下性质的二叉排序树:

(1)左右子树深度之差的绝对值不超过1;

(2)左右子树仍然为平衡二叉树.

  平衡因子BF=左子树深度-右子树深度.

平衡二叉树每个结点的平衡因子只能是1,0,-1。若其绝对值超过1,则该二叉排序树就是不平衡的。

1.1 思想

若向平衡二叉树中插入一个新结点后破坏了平衡二叉树的平衡性。首先要找出插入新结点后失去平衡的最小子树根结点的指针。然后再调整这个子树中有关结点之间的链接关系,使之成为新的平衡子树。当失去平衡的最小子树被调整为平衡子树后,原有其他所有不平衡子树无需调整,整个二叉排序树就又成为一棵平衡二叉树。
失去平衡的最小子树是指以离插入结点最近,且平衡因子绝对值大于1的结点作为根的子树。假设用A表示失去平衡的最小子树的根结点,则调整该子树的操作可归纳为下列四种情况。

LL型旋转

由于在A的左孩子B的左子树上插入结点F,使A的平衡因子由1增至2而失去平衡。故需进行一次顺时针旋转操作。 即将A的左孩子B向右上旋转代替A作为根结点,A向右下旋转成为B的右子树的根结点。而原来B的右子树则变成A的左子树。
这里写图片描述

RR型旋转

由于在A的右孩子C 的右子树上插入结点F,使A的平衡因子由-1减至-2而失去平衡。故需进行一次逆时针旋转操作。即将A的右孩子C向左上旋转代替A作为根结点,A向左下旋转成为C的左子树的根结点。而原来C的左子树则变成A的右子树。
这里写图片描述

LR型旋转

由于在A的左孩子B的右子数上插入结点F,使A的平衡因子由1增至2而失去平衡。故需进行两次旋转操作(先逆时针,后顺时针)。即先将A结点的左孩子B的右子树的根结点D向左上旋转提升到B结点的位置,然后再把该D结点向右上旋转提升到A结点的位置。即先使之成为LL型,再按LL型处理。
这里写图片描述

RL型旋转

由于在A的右孩子C的左子树上插入结点F,使A的平衡因子由-1减至-2而失去平衡。故需进行两次旋转操作(先顺时针,后逆时针),即先将A结点的右孩子C的左子树的根结点D向右上旋转提升到C结点的位置,然后再把该D结点向左上旋转提升到A结点的位置。即先使之成为RR型,再按RR型处理。
这里写图片描述

小结

1.其实还有2个最基本的旋转L型旋转和R型旋转,比较简单就不在说了
2.LL型和RR型其实是对称的,同理LR和RL也是
3.LL型不是说先L旋转再L旋转,其意思是导致不平衡的原因来自该节点的L孩子的L孩子,其对应的旋转方式

2 红黑树

红黑树是一种很有意思的平衡检索树。它的统计性能要好于平衡二叉树(有些书籍根据作者姓名,Adelson-Velskii和Landis,将其称为AVL-树),因此,红黑树在很多地方都有应用。java的TreeMap就是基于红黑树,在C++ STL中,很多部分(目前包括set, multiset, map, multimap)应用了红黑树的变体(SGI STL中的红黑树有一些变化,这些修改提供了更好的性能,以及对set操作的支持)。

红黑树的定义如下:

满足下列条件的二叉搜索树是红黑树
* 每个结点要么是“红色”,要么是“黑色”(后面将说明)
* 所有的叶结点都是空结点,并且是“黑色”的
* 如果一个结点是“红色”的,那么它的两个子结点都是“黑色”的
* (注:也就是說,如果結點是黑色的,那么它的子節點可以是紅色或者是黑色的)。
* 结点到其子孙结点的每条简单路径都包含相同数目的“黑色”结点
* 根结点永远是“黑色”的

之所以称为红黑树的原因就在于它的每个结点都被“着色”为红色或黑色。这些结点颜色被用来检测树的平衡性。但需要注意的是,红黑树并不是平衡二叉树,恰恰相反,红黑树放松了平衡二叉树的某些要求,由于一定限度的“不平衡”,红黑树的性能得到了提升。
##思想
从根结点到叶结点的黑色结点数被称为树的“黑色高度”(black-height)。前面关于红黑树的性质保证了从根结点到叶结点的路径长度不会超过任何其他路径的两倍。

我们来解释一下这个结论。考虑一棵黑色高度为3的红黑树:从根结点到叶结点的最短路径长度显然是2(黑-黑-黑),最长路径为4(黑-红-黑-红- 黑)。由于性质4,不可能在最长路经中加入更多的黑色结点,此外根据性质3,红色结点的子结点必须是黑色的,因此在同一简单路径中不允许有两个连续的红色结点。综上,我们能够建立的最长路经将是一个红黑交替的路径。

由此我们可以得出结论:对于给定的黑色高度为n的红黑树,从根到叶结点的简单路径的最短长度为n-1,最大长度为2(n-1)。

插入和删除操作中,结点可能被旋转以保持树的平衡。红黑树的平均和最差搜索时间都是O(log2 n)。Cormen [2001]给出了对于这一结论的证明。在实际应用中,红黑树的统计性能要好于平衡二叉树,但极端性能略差。

红黑树中结点的插入过程

插入结点的过程是:

  • 在树中搜索插入点
  • 新结点将替代某个已经存在的空结点,并且将拥有两个作为子结点的空结点
  • 新结点标记为红色,其父结点的颜色根据红黑树的定义确定;如果需要,对树作调整

注意 空结点和NULL指针是不同的。在简单的实现中,可以使用作为“监视哨”,标记为黑色的公共结点作为前面提到的空结点。

给一个红色结点加入两个空的子结点符合性质4,同时,也必须确保红色结点的两个子结点都是黑色的(根据性质3)。尽管如此,当新结点的父结点时红色时,插入红色的子结点将是违反定义的。这时存在两种情况。

情况1:插入的是根结点。

原树是空树,此情况只会违反性质2。
对策:直接把此结点涂为黑色。

情况2:插入的结点的父结点是黑色。

此不会违反性质2和性质4,红黑树没有被破坏。
对策:什么也不做。

情况3:插入的节点的父节点是红色 (那么其祖父节点必然黑色)

又分为2种情况:红色的父亲节点是祖父节点左孩子或右孩子, 根据对称性,讨论一边就行了,以左孩子为例:
先贴一下伪代码:

1 while color[p[z]] = RED         //父亲节点是红色2     do if p[z] = left[p[p[z]]]      //红色的父亲节点是祖父节点左孩子 ,此时祖父节点必然为黑色3            then y ← right[p[p[z]]]    //y表示叔父节点的颜色4                 if color[y] = RED          //如果叔父节点是红色5                   then color[p[z]] ← BLACK   //父亲节点设置为黑色6                         color[y] ← BLACK       //叔父节点设置为黑色7                         color[p[p[z]]] ← RED     //祖父节点设置为红色8                         z ← p[p[z]]              //移动指针z到祖父节点,判断红黑树的性质是否满足9                 else if z = right[p[z]]              //如果叔父节点是黑色10                      then z ← p[z]                   //如果z指针是父亲节点的右孩子,左旋转11                           LEFT-ROTATE(T, z)             12                      color[p[z]] ← BLACK             //此时z已经是红色父节点的左孩子,把其父节点设置为黑色13                      color[p[p[z]]] ← RED             //祖父节点设置为红色14                      RIGHT-ROTATE(T, p[p[z]])           //右旋转其祖父节点15     else (same as then clause                         with "right" and "left" exchanged)16 color[root[T]] ← BLACK //最后把根节点设置为黑色

3.1 叔父节点为红色,那么把父亲和叔父节点设置为黑色,把祖父节点设置为红色,在把祖父的节点进行判断是否满足红黑树性质。上面的代码3-6行
3.2叔父节点是黑色,这时又要讨论当前节点是否是左孩子,不是的话就要进行左旋转一次,代码9-11行,本身如果就是左孩子就不需要了,如下图:
这里写图片描述
然后,这个是不是看起来有点像平衡二叉树,LL旋转型呢。只是在那个基础上多了一个着色的步奏
这里写图片描述
这样是不是就满足红黑树的性质了

红黑树的删除过程

红黑树的删除远比插入要复杂的多,还是基于算法导论上面进行分析,如普通二叉搜索树一样,需要先将其从二叉树中删除,然后在进行颜色调整,以满足上面的五条性质。
在删除过程中,需要分为四种情况进行考虑:
1. 只有左孩子存在
2. 只有右孩子存在
3. 两孩子均存在
4. 两孩子均不存在
如果为前两种情形时,只需将其对应的孩子的父节点指向其祖父节点,如果是第三种情况,则需要去查找该节点的后继节点,然后利用后继节点来进行替换该节点,第四种情况最简单,直接将该节点对应父节点的孩子置为空。

RB-DELETE(T, z)   单纯删除结点的总操作 1 if left[z] = nil[T] or right[z] = nil[T]  //如果只有左孩子或者右孩子(不包括NULL节点) 2    then y ← z                            //把y指向带删除的节点 3    else y ← TREE-SUCCESSOR(z)            //否则指向y的后继节点,这时z有左右孩子的情况 4 if left[y] ≠ nil[T]                     //如果y的左孩子不为null, 5    then x ← left[y]                     //指向y的左孩子,这个只可能出现在y只有左孩子的情况 6    else x ← right[y]                    //否则指向右孩子,如果z只有右孩子,那么x指向右孩子,其他情况。x都是一个空节点 7 p[x] ← p[y]         //把y的父节点给x,如果z只有左孩子右孩子,则表示用其孩子节点来顶替他,否则就是把y节点设置为空 8 if p[y] = nil[T]     //表示删除的是根节点,那么用x节点设置为根节点 9    then root[T] ← x10    else if y = left[p[y]]     //如果删除的不是根节点,则用x节点来代替点y节点,此时y节点已经和这课树没有任何关系了11            then left[p[y]] ← x12            else right[p[y]] ← x13 if y≠ z     //如果y不等于z,就是3行里面中,y是指向x的后继节点14    then key[z] ← key[y]    //把y的值拷贝到z中,就完成了z节点的删除15         copy y's satellite data into z16 if color[y] = BLACK         17    then RB-DELETE-FIXUP(T, x)   如果y的颜色为黑色,那么说明删除影响了树的平衡,此时以x为节点的子树的高度减少了118 return y

删除x的时候,又有4种情况:
情况1:x的兄弟w是红色的。
情况2:x的兄弟w是黑色的,且w的俩个孩子都是黑色的。
情况3:x的兄弟w是黑色的,w的左孩子是红色,w的右孩子是黑色。
情况4:x的兄弟w是黑色的,且w的右孩子时红色的。

RB-DELETE-FIXUP(T, x) 1 while x ≠ root[T] and color[x] = BLACK          //至少x指向的是一个黑色,那么都有可能导致不平衡, 2     do if x = left[p[x]]                                     //该节点是左孩子 3           then w ← right[p[x]]                          //w表示其兄弟节点                  //Case 1 4                if color[w] = RED                           //如果兄弟节点是红色 5                   then color[w] ← BLACK           //改变w的颜色,和其父节点的颜色 6                        color[p[x]] ← RED                       7                        LEFT-ROTATE(T, p[x])                   //左旋一次,然后就变成下面的3种情况 8                        w ← right[p[x]]                    //以下三种情况就是x的兄弟节点w为黑色,并且其兄弟节点的孩子的3种情况                  //Case 2                      ▹  9                if color[left[w]] = BLACK and color[right[w]] = BLACK10                   then color[w] ← RED          //w的2个孩子都是黑色,那么把w设置为红色11                        x ← p[x]                                    //Case 3                              12                   else if color[right[w]] = BLACK       //w的右孩子是黑色13                           then color[left[w]] ← BLACK       //w的左孩子设置为黑色14                                color[w] ← RED               //w设置为红色15                                RIGHT-ROTATE(T, w)            //右旋转一次16                                w ← right[p[x]]                                  else     //Case 4                       //w的右孩子是红色17                         color[w] ← color[p[x]]         //把x的父节点颜色给w18                         color[p[x]] ← BLACK                   // w设置为黑色19                         color[right[w]] ← BLACK               //w的右孩子设置为黑色20                         LEFT-ROTATE(T, p[x])                  //左旋转一次21                         x ← root[T]                           //退出循环22        else (same as then clause with "right" and "left" exchanged)23 color[x] ← BLACK 

简单分析一下
我们用T(A)表示以A为根节点构成的子树
###情况一:x的兄弟w是红色的。
这里写图片描述
简单说明下:由于T(x)高度减少了1。
所以我们用T(A)旋转一次,并且改变w和A节点颜色,此时,如上图的改变后,该子树的根节点变为w,T(w)的右子树高度没有变,还是n,T(A)的右子树的高度为n,T(A)的左子树高度是n-1,此时则变成了另外三种情况可以处理结构。也就是上图改变后,红色方框包含的子树。
###情况二:x的兄弟w是黑色的,且w的俩个孩子都是黑色的。
这里写图片描述
这种情况比较简单,直接把w设置为红色,T(A)就平衡了,但是如果T(A)只是另一个树的子树,那么整体是不是平衡的呢?比如T(A)是情况一中的那颗,这样处理后,整体是没有平衡的,所以我们需要把x设置为A节点,再继续判断是否平衡。

情况三:x的兄弟w是黑色的,w的左孩子是红色,w的右孩子是黑色

这里写图片描述
这种情况还是可能会引起整体的不平衡。

情况4:x的兄弟w是黑色的,且w的右孩子时红色的.

这里写图片描述
其中w的颜色A中的颜色一样,这样转换就填补的T(x)的高度,这样不会引起整体的不平衡,所以直接退出循环。

小结:

1.插入的时候,先看父节点:父节点是黑色,直接插,如果父节点是红色,则看叔父节点,又分为叔父节点是红色和黑色的情况
2.删除的时候,如果只有一个孩子,则用孩子来顶替,否则,找出其后继节点,来顶替,然后看删除的节点颜色,如果是红色,则不用改变,如果是黑色,则看兄弟节点:兄弟节点为红色,转换为兄弟节点为黑的情况;兄弟节点为黑色,则其兄弟节点的孩子情况,又分有3种情况讨论。

B树和B+树正在编写中:

1 0
原创粉丝点击