为了接下来能更好的学习TreeMap和TreeSet,讲解一下二叉树,AVL树和红黑树。

1. 二叉查找树

在讲AVL树和红黑树之前,作为铺垫必须先说下二叉树。

二叉树本身不必再说,一棵二叉树称为二叉查找树的条件如下:

  1. 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值。
  2. 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值。
  3. 任意节点的左、右子树也分别为二叉查找树。
  4. 没有键值相等的节点。

通常情况下,采用二叉链表作为二叉树的存储结构。中序遍历二叉树可以得到一个关键字的有序序列,一个无序序列可以通过构造一颗二叉查找树变为有序序列,构造树的过程即为对无序序列进行查找的过程。每次插入的新的结点都是二叉查找树上新的叶子结点,在进行插入操作时,不必移动其它结点,只需改动某个结点的指针,由空变为非空即可。搜索、插入、删除的复杂度等于树高,期望O(log n),最坏O(n)(数列有序,树退化成线性表)。

通过改进二叉树,保持二叉树的树高为O(log n),可以使二叉树的操作复杂度稳定于O(log n)。因此,产生了AVL树和红黑树。

2. AVL树

AVL树通过树旋转操作实现了维护树高的目的。AVL树中任意节点的两个子树高度差(平衡因子)最大为1,当平衡因子大于1时,该AVL树需要进行树旋转。

2.1. 树旋转

2.1.1. 左旋和右旋

维基上的一个图能够清晰地表述左旋和右旋:

        +---+                          +---+
| Q | | P |
+---+ +---+
/ \ right rotation / \
+---+ +---+ -------------> +---+ +---+
| P | | Z | | X | | Q |
+---+ +---+ <------------- +---+ +---+
/ \ left rotation / \
+---+ +---+ +---+ +---+
| X | | Y | | Y | | Z |
+---+ +---+ +---+ +---+

可以看到,左旋的步骤如下:

  1. 选择需要旋转的树的新的根节点(图中为Q)。
  2. 将选取的节点作为新的根节点,其父节点变为左子节点,其左子节点变为新的左子节点的右子节点。
  3. 将新的根节点连接到原根节点的父节点上。

右旋步骤和左旋步骤类似,只是方向相反。左右旋是互逆操作。

2.1.2. 左左,右右,左右,右左

在AVL树中需要树旋转的四种情况旋转方法如下:

  • 左左:以中间节点为新的根节点右旋。
  • 右右:以中间节点为新的根节点左旋。
  • 左右:以最下节点为中心进行一次左旋,变为左左,再以新的中间节点为中心进行一次右旋。
  • 右左:以最下节点为中心进行一次右旋,变为右右,再以新的中间节点为中心进行一次左旋。

操作示意如图:

2.2. 删除

从AVL树中删除,可以通过把要删除的节点向下旋转成为一个叶子节点,接着直接移除这个叶子节点。因为旋转成叶子节点期间最多有logn个节点被旋转,因此,AVL删除节点耗费O(logn)时间。

3. 红黑树

红黑树和AVL树一样,都是在查找删除时进行特定操作以维持高性能的特定的平衡二叉树。它可以在O(logn)时间内查找,插入和删除。红黑树相对于普通的二叉树,其性质如下:

  1. 红黑树的每个节点都有颜色,为红色(R)或黑色(B),也成RB树。
  2. 红黑树的根节点为黑色。
  3. 红黑树的每个叶节点(即NIL节点,也叫空节点)为黑色。
  4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有节点没有连续的红色)
  5. 从任意节点到每个叶子所在的路径都包含相同数目的黑色。

3.1. 插入

插入节点有以下几个关键点:

  • 插入节点总是红色节点。
  • 如果插入节点的父节点是黑色,能维持性质。
  • 如果插入节点的父节点是红色,破坏了性质,要通过旋转或重新着色来维持性质。

插入时,我们按照二叉树的插入来运行,如果我们插入了根节点,由于插入点是红色,则破坏了性质2,如果父节点是红色,则破坏性质4。

因此,插入的伪代码如下:

RB-INSERT(T, z)
y ← nil
x ← T.root
while x ≠ T.nil
do y ← x
if z.key < x.key
then x ← x.left
else x ← x.right
z.p ← y
if y == nil[T]
then T.root ← z
else if z.key < y.key
then y.left ← z
else y.right ← z
z.left ← T.nil
z.right ← T.nil
z.color ← RED
RB-INSERT-FIXUP(T, z)

现在详细解释一下伪代码。考虑各种插入情况和应对方案:

  • 插入的是跟节点:原树为空树,违反了性质2。直接涂黑。
  • 插入的节点父节点是黑色:未违反任何性质。

以上两种情况比较简单,接下来介绍三种比较复杂的情况。

  • 插入的节点的父节点是红色,且祖父节点的另一个节点(叔叔节点)是红色:将当前节点的父节点和叔叔节点变为黑色,祖父节点变为红色,让当前节点指向祖父节点,重新进行判断。下面图片演示了该变化过程。



  • 插入的节点的父节点是红色,且祖父节点的另一个节点(叔叔节点)是黑色,当前节点是父节点的左(右)子节点同时父节点是祖父节点的右(左)节点:将当前节点的父节点作为新的当前节点,之后,将新当前节点和其子节点即原当前节点部分进行右(左)旋转,此后,重新进行判定。

  • 插入的节点的父节点是红色,且祖父节点的另一个节点(叔叔节点)是黑色,当前节点是父节点的左(右)子节点同时父节点是祖父节点的左(右)节点:父节点变为黑色,祖父节点变为红色,祖父节点和父节点部分进行右旋。

3.2. 删除

删除的节点的方法与常规二叉搜索树中删除节点的方法是一样的,即,如果它有不足两个非空子节点,则直接用其子节点替代/直接删除。如过它有两个非空子节点,则用左树最大节点/右树最小节点进行替换后进行修复。

和插入类似,删除也有多种情况。伪代码如下:

while x ≠ root[T] and color[x] = BLACK
do if x = left[p[x]]
then w ← right[p[x]]
if color[w] = RED
then color[w] ← BLACK ▹ Case 1
color[p[x]] ← RED ▹ Case 1
LEFT-ROTATE(T, p[x]) ▹ Case 1
w ← right[p[x]] ▹ Case 1
if color[left[w]] = BLACK and color[right[w]] = BLACK
then color[w] ← RED ▹ Case 2
x ← p[x] ▹ Case 2
else if color[right[w]] = BLACK
then color[left[w]] ← BLACK ▹ Case 3
color[w] ← RED ▹ Case 3
RIGHT-ROTATE(T, w) ▹ Case 3
w ← right[p[x]] ▹ Case 3
color[w] ← color[p[x]] ▹ Case 4
color[p[x]] ← BLACK ▹ Case 4
color[right[w]] ← BLACK ▹ Case 4
LEFT-ROTATE(T, p[x]) ▹ Case 4
x ← root[T] ▹ Case 4
else (same as then clause with "right" and "left" exchanged)
color[x] ← BLACK

从伪代码我们可以考虑各种情况。因为该点为替换而来的原叶子节点,所以必为黑色。

  • 该点为根节点:什么都不用做。
  • 该点的兄弟节点为红色:将兄弟节点染黑,父节点染红,并将二者部分进行左旋(若兄弟节点为左节点则右旋)。
  • 该点的兄弟节点为黑色且兄弟节点的两个子节点均为黑色:兄弟节点涂黑,当前节点变为当前节点的父节点,重新判断。
  • 该点的兄弟节点为黑色且兄弟节点的左子节点为红色,右子节点为黑色(若兄弟节点为左节点则相反):兄弟节点左子节点变为黑色,兄弟节点变为红色,将二者进行一次右旋(若兄弟节点为左节点则颜色旋转方向相反)。
  • 该点的兄弟节点为黑色且兄弟节点的左子为黑色(若兄弟节点为左节点则为红色):把兄弟节点颜色染为父节点颜色,父节点颜色和兄弟节点的右子节点(若兄弟节点为左节点则相反)染为黑色,然后将二者进行左旋(兄弟为左节点则右旋),算法结束。

4. 参考文章

AVL树

红黑树

教你初步了解红黑树

二叉树,AVL树和红黑树的更多相关文章

  1. 单例模式,堆,BST,AVL树,红黑树

    单例模式 第一种(懒汉,线程不安全): public class Singleton { private static Singleton instance; private Singleton () ...

  2. [BinaryTree] AVL树、红黑树、B/B+树和Trie树的比较

    转自:AVL树.红黑树.B/B+树和Trie树的比较 AVL树 最早的平衡二叉树之一.AVL是一种高度平衡的二叉树,所以通常的结果是,维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应 ...

  3. Mysql为什么使用b+树,而不是b树、AVL树或红黑树?

    首先,我们应该考虑一个问题,数据库在磁盘中是怎样存储的?(答案写在下一篇文章中) b树.b+树.AVL树.红黑树的区别很大.虽然都可以提高搜索性能,但是作用方式不同. 通常文件和数据库都存储在磁盘,如 ...

  4. 论AVL树与红黑树

    首先讲解一下AVL树: 例如,我们要输入这样一串数字,10,9,8,7,15,20这样一串数字来建立AVL树 1,首先输入10,得到一个根结点10 2,然后输入9, 得到10这个根结点一个左孩子结点9 ...

  5. B树,B+树,红黑树应用场景AVL树,红黑树,B树,B+树,Trie树

    B B+运用在file system database这类持续存储结构,同样能保持lon(n)的插入与查询,也需要额外的平衡调节.像mysql的数据库定义是可以指定B+ 索引还是hash索引. C++ ...

  6. AVL树与红黑树

    平衡树是平时经常使用数据结构. C++/JAVA中的set与map都是通过红黑树实现的. 通过了解平衡树的实现原理,可以更清楚的理解map和set的使用场景. 下面介绍AVL树和红黑树. 1. AVL ...

  7. AVL树,红黑树,B-B+树,Trie树原理和应用

    前言:本文章来源于我在知乎上回答的一个问题 AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中? 看完后您可能会了解到这些数据结构大致的原理及为什么用在这些场景,文章并不涉及具体操作 ...

  8. AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中?

    AVL树: 最早的平衡二叉树之一.应用相对其他数据结构比较少.windows对进程地址空间的管理用到了AVL树. 红黑树: 平衡二叉树,广泛用在C++的STL中.如map和set都是用红黑树实现的. ...

  9. 对于AVL树和红黑树的理解

    AVL又称(严格)高度平衡的二叉搜索树,也叫二叉查找树.平衡二叉树.window对进程地址空间的管理用到了AVL树. 红黑树是非严格平衡二叉树,统计性能要好于平衡二叉树.广泛的在C++的STL中,ma ...

随机推荐

  1. Java异常处理示例

    翻译人员: 铁锚 翻译日期: 2013年11月22日 原文链接: Java Exception Handling Example 本文中有两个示例, 第一个演示了所有调用其他方法的地方,都必须处理被调 ...

  2. SpriteBuilder使用Shader Effect的另一种方法

    记住你并不是必须要使用Effect节点去给一个特定的精灵应用效果. 你只要选择一个精灵然后切换至项目属性窗口(Item Properties tab),找到effects用户接口在CCSprite属性 ...

  3. 一个App与另一个App之间的交互,添加了自己的一些理解

    URL Scheme 是什么? iOS有个特性就是应用将其自身"绑定"到一个自定义 URL scheme 上,该 scheme用于从浏览器或其他应用中启动本应用.常见的分享到第三方 ...

  4. VectorDrawable与AnimatedVectorDrawable

    VectorDrawable  Android L开始提供了新的API VectorDrawable 可以使用SVG类型的资源,也就是矢量图.先来一个例子吧. <?xml version=&qu ...

  5. *** non-numeric second argument to `wordlist' function: ''. Stop错误解决办法

    PS: 解决办法搜集自:stackoverflow website:http://stackoverflow.com/questions/5677178/ndk-gdb-fails-with-mess ...

  6. 网站开发进阶(六)JSP两种声明变量的区别

    JSP两种声明变量的区别 在JSP中用两种声明变量的方法,一种是在<%! %>内,一种是在<% %>内.他们之间有什么区别呢?我们直接看一个JSP文件来理解. 代码如下: &l ...

  7. FFmpeg与VS2010

    编译FFmpeg是一件痛苦的事情,一般都直接使用Zeranoe FFmpeg Builds. 如果使用这个版本,需要注意ffmpeg的帮助里的一段话: To create import librari ...

  8. python简单线程和协程学习

    python中对线程的支持的确不够,不过据说python有足够完备的异步网络框架模块,希望日后能学习到,这里就简单的对python中的线程做个总结 threading库可用来在单独的线程中执行任意的p ...

  9. MySQL 菜鸟入门“秘籍”

    一.MySQL简介 1.什么是数据库 ? 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库,它产生于距今六十多年前,随着信息技术和市场的发展,特别是二十世纪九十年代以后,数据管理不 ...

  10. javah tool for Android Native Application

    javah可以在Eclipse中配置成为External Tools,选择External Tools Configurations,配置如下,经过测试通过. Location: ${system_p ...