二叉树,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 ...
随机推荐
- Java的字符串分割的不同实现
在java中实现字符串的分割相对而言是很简单的.我们一般会采取两中方式.一个是从jdk1.1就开始的StringTokenizer类,另一个是调用split方法进行分割.下面请看代码: import ...
- sed命令 linux
sed 实用工具是一个"编辑器",但它与其它大多数编辑器不同.除了不面向屏幕之外,它还是非交互式的.这意味着您必须将要对数据执行的命令插入到命令行或要处 理的脚本中.当显示它时,请 ...
- Spring揭秘 读书笔记 五 容器的启动
Spring的IoC容器所起的作用,就是生产bean,并维持bean间的依赖关系.它会以某种方式加载Configuration Metadata(通常也就是XML格式的配置信息),然后根据这些信息绑定 ...
- sublime text (ST)一篇通(安装、配置、扩展、使用)
sublime编辑器,功能插件多,可以扩展为IDE------------------------------------------- 1.安装 官网下载 http://www.sublimete ...
- 【嵌入式开发】C语言 命令行参数 函数指针 gdb调试
. 作者 : 万境绝尘 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/21551397 | http://www.hanshul ...
- TableLayout和Viewpager实现切换
因为我是在之前的基础上写的,所以这个TableLayout和Viewpager实际上是写在Fragment上的.要写到Activity里其实也是一样的啦. 先看效果图,原谅我不会动态图,只能截个图啦 ...
- 安卓笔记-- popupwindow back键不消失的问题
// 可能是一个bug ,如果不设置背景,触摸焦点外和back键都不会消失,需如下设置,并不会影响你的背景 popupWindow.setBackgroundDrawable(new ...
- 《java入门第一季》之面向对象面试题(继承中构造方法的关系)
/* 继承中构造方法的关系 A:子类中所有(子类的有参和无参)的构造方法(默认都会访问父类)中(空参数)的构造方法,默认访问父类空参数构造,不默认访问有参数构造 B:为什么呢? 因为子类会继承父类中的 ...
- 关于Android的https通讯安全
原文链接:http://pingguohe.net/2016/02/26/Android-App-secure-ssl.html 起因 前段时间,同事拿着一个代码安全扫描出来的 bug 过来咨询,我一 ...
- linux命令大全(自己慢慢看)
http://blog.zol.com.cn/874/article_873769.html rm -rf mydir /* 删除mydir目录 */ cd mydir /* 进入mydir目录 */ ...