二叉树,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 ...
随机推荐
- XBMC源代码简析 5:视频播放器(dvdplayer)-解复用器(以ffmpeg为例)
XBMC分析系列文章: XBMC源代码分析 1:整体结构以及编译方法 XBMC源代码分析 2:Addons(皮肤Skin) XBMC源代码分析 3:核心部分(core)-综述 XBMC源代码分析 4: ...
- 14_Android中Service的使用,关于广播接收者的说明
服务:长期后台运行的没有界面的组件 android应用:什么地方需要用到服务? 天气预报:后台的连接服务器的逻辑,每隔一段时间获取最新的天气信息 股票显示:后台的连接服务器的逻辑,每隔一段时间获 ...
- 【翻译】Ext JS最新技巧——2014-8-13
原文:Top Support Tips Greg Barry:新的框架. 新的文档类型(Doctype) 在Ext JS 5,只支持IE8+,因此不再古力用户使用严格的HTML文档类型.现在,推荐使用 ...
- Linux下编译GDAL
一.准备工作 从官网下载GDAL.PROJ.4和GEOS,将其存放在/home/liml/Work/3rdPart目录并解压,如下图所示.下载地址请自行Google.注:使用的系统是CentOS6.4 ...
- Xcode调试非异常导致崩溃的程序
如果App不是因为一个异常而崩溃,Xcode可能任然会指向main()函数为出错位置. 在这种情况下,你可能遇上了更低级别的问题.也许是一个除以0错误或是缓冲溢出问题,或者你寻址一个已经被释放的对象. ...
- python模块 - 常用模块推荐
http://blog.csdn.net/pipisorry/article/details/47185795 python常用模块 压缩字符 当谈起压缩时我们通常想到文件,比如ZIP结构.在Pyth ...
- SpriteBuilder物理对象的父子关系
注意:打开物理使能(Physics-enabled)的节点忽略他们的父节点关系. 这意味着,一个物理使能的子节点不会随着其父节点移动. 物理引擎对于节点父子关系这个概念毫不知情,因此单独的对待所有物理 ...
- Android群英传笔记——第三章:Android控件架构与自定义控件讲解
Android群英传笔记--第三章:Android控件架构与自定义控件讲解 真的很久没有更新博客了,三四天了吧,搬家干嘛的,心累,事件又很紧,抽时间把第三章大致的看完了,当然,我还是有一点View的基 ...
- android JNI调用机制
JNI的出现使得开发者既可以利用Java语言跨平台.类库丰 富.开发便捷等特点,又可以利用Native语言的高效. JNI是JVM实现中的一部分,因此Native语言和Java代码都运行在JVM的宿主 ...
- 使用代码刷QQ的跨年红包
使用的库从这里找: https://github.com/GameTerminator/dont-touch-white 关键代码就是一个while循环加上drag. import com.andro ...