数据结构中的树
声明:
尊重原创,转载请注明出处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] ← x
10 else if y = left[p[y]] //假设删除的不是根节点。则用x节点来取代点y节点,此时y节点已经和这课树没有不论什么关系了
11 then left[p[y]] ← x
12 else right[p[y]] ← x
13 if y≠ z //假设y不等于z,就是3行里面中。y是指向x的后继节点
14 then key[z] ← key[y] //把y的值复制到z中,就完毕了z节点的删除
15 copy y's satellite data into z
16 if color[y] = BLACK
17 then RB-DELETE-FIXUP(T, x) 假设y的颜色为黑色,那么说明删除影响了树的平衡。此时以x为节点的子树的高度降低了1
18 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]] = BLACK
10 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的父节点颜色给w
18 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+树正在编写中: