二叉树,AVL树和红黑树
为了接下来能更好的学习TreeMap和TreeSet,讲解一下二叉树,AVL树和红黑树。
1. 二叉查找树
在讲AVL树和红黑树之前,作为铺垫必须先说下二叉树。
二叉树本身不必再说,一棵二叉树称为二叉查找树的条件如下:
- 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值。
- 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值。
- 任意节点的左、右子树也分别为二叉查找树。
- 没有键值相等的节点。
通常情况下,采用二叉链表作为二叉树的存储结构。中序遍历二叉树可以得到一个关键字的有序序列,一个无序序列可以通过构造一颗二叉查找树变为有序序列,构造树的过程即为对无序序列进行查找的过程。每次插入的新的结点都是二叉查找树上新的叶子结点,在进行插入操作时,不必移动其它结点,只需改动某个结点的指针,由空变为非空即可。搜索、插入、删除的复杂度等于树高,期望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 |
+---+ +---+ +---+ +---+
可以看到,左旋的步骤如下:
- 选择需要旋转的树的新的根节点(图中为Q)。
- 将选取的节点作为新的根节点,其父节点变为左子节点,其左子节点变为新的左子节点的右子节点。
- 将新的根节点连接到原根节点的父节点上。
右旋步骤和左旋步骤类似,只是方向相反。左右旋是互逆操作。
2.1.2. 左左,右右,左右,右左
在AVL树中需要树旋转的四种情况旋转方法如下:
- 左左:以中间节点为新的根节点右旋。
- 右右:以中间节点为新的根节点左旋。
- 左右:以最下节点为中心进行一次左旋,变为左左,再以新的中间节点为中心进行一次右旋。
- 右左:以最下节点为中心进行一次右旋,变为右右,再以新的中间节点为中心进行一次左旋。
操作示意如图:

2.2. 删除
从AVL树中删除,可以通过把要删除的节点向下旋转成为一个叶子节点,接着直接移除这个叶子节点。因为旋转成叶子节点期间最多有logn个节点被旋转,因此,AVL删除节点耗费O(logn)时间。
3. 红黑树
红黑树和AVL树一样,都是在查找删除时进行特定操作以维持高性能的特定的平衡二叉树。它可以在O(logn)时间内查找,插入和删除。红黑树相对于普通的二叉树,其性质如下:
- 红黑树的每个节点都有颜色,为红色(R)或黑色(B),也成RB树。
- 红黑树的根节点为黑色。
- 红黑树的每个叶节点(即NIL节点,也叫空节点)为黑色。
- 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有节点没有连续的红色)
- 从任意节点到每个叶子所在的路径都包含相同数目的黑色。
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树和红黑树的更多相关文章
- 单例模式,堆,BST,AVL树,红黑树
单例模式 第一种(懒汉,线程不安全): public class Singleton { private static Singleton instance; private Singleton () ...
- [BinaryTree] AVL树、红黑树、B/B+树和Trie树的比较
转自:AVL树.红黑树.B/B+树和Trie树的比较 AVL树 最早的平衡二叉树之一.AVL是一种高度平衡的二叉树,所以通常的结果是,维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应 ...
- Mysql为什么使用b+树,而不是b树、AVL树或红黑树?
首先,我们应该考虑一个问题,数据库在磁盘中是怎样存储的?(答案写在下一篇文章中) b树.b+树.AVL树.红黑树的区别很大.虽然都可以提高搜索性能,但是作用方式不同. 通常文件和数据库都存储在磁盘,如 ...
- 论AVL树与红黑树
首先讲解一下AVL树: 例如,我们要输入这样一串数字,10,9,8,7,15,20这样一串数字来建立AVL树 1,首先输入10,得到一个根结点10 2,然后输入9, 得到10这个根结点一个左孩子结点9 ...
- B树,B+树,红黑树应用场景AVL树,红黑树,B树,B+树,Trie树
B B+运用在file system database这类持续存储结构,同样能保持lon(n)的插入与查询,也需要额外的平衡调节.像mysql的数据库定义是可以指定B+ 索引还是hash索引. C++ ...
- AVL树与红黑树
平衡树是平时经常使用数据结构. C++/JAVA中的set与map都是通过红黑树实现的. 通过了解平衡树的实现原理,可以更清楚的理解map和set的使用场景. 下面介绍AVL树和红黑树. 1. AVL ...
- AVL树,红黑树,B-B+树,Trie树原理和应用
前言:本文章来源于我在知乎上回答的一个问题 AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中? 看完后您可能会了解到这些数据结构大致的原理及为什么用在这些场景,文章并不涉及具体操作 ...
- AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中?
AVL树: 最早的平衡二叉树之一.应用相对其他数据结构比较少.windows对进程地址空间的管理用到了AVL树. 红黑树: 平衡二叉树,广泛用在C++的STL中.如map和set都是用红黑树实现的. ...
- 对于AVL树和红黑树的理解
AVL又称(严格)高度平衡的二叉搜索树,也叫二叉查找树.平衡二叉树.window对进程地址空间的管理用到了AVL树. 红黑树是非严格平衡二叉树,统计性能要好于平衡二叉树.广泛的在C++的STL中,ma ...
随机推荐
- Linux信号实践(5) --时间与定时器
三种不同精度的睡眠 1.sleep #include <unistd.h> unsigned int sleep(unsigned int seconds); RETURN VALUE Z ...
- android studio中使用lambda
环境准备 如果还没有安装Java 8,那么你应该先安装才能使用lambda和stream(译者建议在虚拟机中安装,测试使用). 像NetBeans 和IntelliJ IDEA 一类的工具和IDE就支 ...
- [WinForm]C# .net防止一个程序(WinForm)重复运行的方法。
最近比较忙,邮件预警系统暂停了没时间去处理,临时处理:直接执行exe文件! 可是问题来了: 我点击了两次,原来几乎在同时执行这个进程,我在程序中有线程时间睡眠2秒一次等待队列,打开进程果然两个MAIL ...
- Ajax核心--XMLHttpRequest对象
XMLHttpRequest 对象是AJAX功能的核心,学习XMLHttpRequest对象就先从创建XMLHttpRequest 对象开始,了解在不同的浏览器中创建XMLHttpRequest 对象 ...
- (NO.00001)iOS游戏SpeedBoy Lite成形记(二十七)
切换回Xcode,在GameScene.m中添加新的实例变量:_winLayer. 接下来在第一个选手到达终点时,我们可以完成选手胜利的动画特效了. 首先,在GameScene.m中添加一个新方法pl ...
- 衡量android开发者水平的面试问题-android学习之旅(91)
一般面试时间短则30分钟,多则1个小时,这么点时间要全面考察一个人难度很大,需要一些技巧,这里我不局限于回答题主的问题,而是分享一下我个人关于如何做好Android技术面试的一些经验: 面试前的准备 ...
- 【Coding算法导论】第4章:最大子数组问题
Coding算法导论 本系列文章主要针对算法导论一书上的算法,将书中的伪代码用C++实现 代码未经过大量数据测试,如有问题,希望能在回复中指出! (一)问题描述 给定一个数组,求数组中连续的子数组的和 ...
- 【Qt编程】Qt学习笔记<一>
1. 在创建项目时,项目名和路径中都不能出现中文. 2. 可以使用Ctrl + "+"和Ctrl + "-"来改变程序的字体大小(Ctrl+ ...
- Obj-C中内存的管理一瞥
注意,ARC仅仅(自动)释放你手工管理的Objective-C类实例的内存, 但是不会释放由C函数或者Core Foundation(Cocoa的底层,C语言的变体)申请的内存.
- plsql 导入导出表、数据、序列、视图
一.导出: 1.打开plsql-->工具---->导出用户对象(可以导出表结构和序列.视图) ps:如果上面不选中"包括所有者",这样到导出的表结构等就不包含所有 ...