图解:平衡二叉树,AVL树
学习过了二叉查找树,想必大家有遇到一个问题。例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况。有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本。而只有建立的树如图2,才能够最大地体现二叉树的优点。
在上述的例子中,图2就是一棵平衡二叉树。科学家们提出平衡二叉树,就是为了让树的查找性能得到最大的体现(至少我是这样理解的,欢迎批评改正)。下面进入今天的正题,平衡二叉树。
AVL的定义
平衡二叉查找树:简称平衡二叉树。由前苏联的数学家Adelse-Velskil和Landis在1962年提出的高度平衡的二叉树,根据科学家的英文名也称为AVL树。它具有如下几个性质:
- 可以是空树。
- 假如不是空树,任何一个结点的左子树与右子树都是平衡二叉树,并且高度之差的绝对值不超过1
平衡之意,如天平,即两边的分量大约相同。如定义,假如一棵树的左右子树的高度之差超过1,如左子树的树高为2,右子树的树高为0,子树树高差的绝对值为2就打破了这个平衡。如依次插入1,2,3三个结点(如下图)后,根结点的右子树树高减去左子树树高为2,树就失去了平衡。
那么在建立树的过程中,我们如何知道左右子树的高度差呢?在这里我们采用了平衡因子进行记录。
平衡因子:左子树的高度减去右子树的高度。由平衡二叉树的定义可知,平衡因子的取值只可能为0,1,-1.分别对应着左右子树等高,左子树比较高,右子树比较高。如下图
说到这里,我们已经可以大概知道平衡二叉树的结构定义需要什么内容了,数据成员,平衡因子,以及左右分支。所以,我们给出如下的结构定义。大家主要首先先了解平衡因子的各个取值及其含义即可。
typedef char KeyType; //关键字
typedef struct MyRcdType //记录
{
KeyType key;
}RcdType,*RcdArr;
typedef enum MyBFStatus //为了方便平衡因子的赋值,这里进行枚举
{ //RH,EH,LH分别表示右子树较高,左右子树等高,左子树较高
RH,EH,LH
}BFStatus;
typedef struct MyBBSTNode //树结点类型定义
{
RcdType data; //数据成员
BFStatus bf; //平衡因子
struct MyBBSTNode *lchild,*rchild; //左右分支
}BBSTNode,*BBSTree;
AVL树的插入时的失衡与调整
前言:这部分的失衡调整是指插入时的失衡与调整。删除的失衡与调整与插入大致一样,但是还是有很多不同,在后续章节讲解。
一、 失衡与调整的引导
说了这么久,我们开始进入今天的重点,如何将一棵不平衡的二叉树变成平衡二叉树(只讨论不平衡的是因为假如树是平衡的就不必我们进行处理)。平衡二叉树的失衡调整主要是通过旋转最小失衡子树来实现的。
最小失衡子树:在新插入的结点向上查找,以第一个平衡因子的绝对值超过1的结点为根的子树称为最小不平衡子树。也就是说,一棵失衡的树,是有可能有多棵子树同时失衡的,如下。而这个时候,我们只要调整最小的不平衡子树,就能够将不平衡的树调整为平衡的树。
在图7中。2结点(左子树树高-右子树树高)的绝对值=2。同理,3结点的平衡因子也为2.此时同时存在了两棵不平衡子树,而以3为根的树是最小的不平衡子树。我们只要将其以3为中心,将最小不平衡树向左旋转,即可得到平衡二叉树,如图8。具体方法后续讲解。
下面我们先用两个简单的例子来感受一下调整的方法。
例1:右子树过高,向左旋转。步骤如下
i. 将2作为根结点
ii. 将1作为2的左孩子
iii. 将2的左孩子作为1的右孩子(维护树的有序性,只是此处为NULL而已)
例2:左子树过高,向右旋转。步骤如下
i. 将2作为根结点
ii. 将3作为2的右孩子
iii. 将2的右孩子作为3的左孩子(维护树的有序性,只是此处为NULL而已)
下面我们再来看一个通过旋转,但是没办法达到平衡的失败例子。
例3:右子树过高,向左旋转。步骤如下
i. 将3作为根结点
ii. 将3的左孩子作为1的右孩子
iii. 将1作为3的左孩子
如上,我们发现,旋转之后树并没有恢复平衡。对比图9,我们发现,根的右子树不一致。
在上面的三个例子我们可以看出,我们对不平衡的树进行旋转的时候,不仅需要考虑需要最小失衡子树的根结点的平衡因子,还要考虑根结点较高子树的根结点的平衡因子。如图9与图11中,较高子树都为右子树,右子树不同,旋转后有着完全不同的结果。
为了方便讨论,我们使用连续的两个字母来表示平衡因子,以表示各种不同的情况。第一个字母表示最小不平衡子树根结点的平衡因子,第二个字母表示最小不平衡子树较高子树的根结点的平衡因子。使用L表示左子树较高,R表示右子树较高,E表示左右子树等高。如上述图11,根为的平衡因子L,较高子树的根为L,我们将这种情况表示为LL型,再如上述例子3,根为R,较高子树的根为L我们将这种情况称为RL型。
下面我们将对所有的失衡情况进行讨论。大致分为两大类,一左子树过高,二右子树过高。顺带提一下记忆的方法,读者对于具体某一种类型只要记住最后哪一个结点作为根即可,也就是下面标红色的部分。
二、失衡与处理详解
1. 左子树过高
a) LL型
在LL型的不平衡树中,我们首先找到最小不平衡子树,再以其根结点向右旋转。为何是向右旋转呢?应该不难理解,向右旋转后,相当于右边的子树树高增加了1,而左边的子树树高降低了1,而原本的树高之差为2,那么就能够将根的平衡因子就化为0.引用一下之前的图如下。旋转之后为“原来根结点的左孩子作为新的根结点”。
我们对树以根结点为中心,向右旋转。旋转步骤如下
i. 将2作为根结点
ii. 将3作为2的右孩子
iii. 将2的右孩子作为3的左孩子(维护树的有序性,只是此处为NULL而已)
旋转后,3与2的平衡因子为EH,1的平衡因子保持不变。
b) LE型
在这里需要说明的是,插入的时候,是不会出现LE的这种情况的。只有在删除的时候才会出现。下面对于为何插入不可能出现做一些个人见解。
我们不妨假设存在LE的这种情况。如下。
假设我们刚插入的元素是1,那么原来的树已经不是平衡树。不可能。
假设我们刚插入的元素是2.5,那么原来的树也不是平衡树,也不可能。所以说在插入的时候,是不会出现LE的这种情况的。而具体什么时候会出现呢,我们在删除的章节进行讲解。同理,不可能出现RE的情况,下面也不进行讨论。读者可以使用反证法自行验证。
c) LR型
对于LR,要分为两步进行旋。旋转之后为“原来根结点的左孩子的右孩子作为新的根结点”。
第一以较高子树的根,即1,为中心向左旋转。具体步骤如下。
i. 将2的左子树作为1的右子树(维护树的有序性,只是此处为NULL而已)
ii. 将1作为2的左子树
iii. 将2作为3的左子树
第二以原树的根,即3为中心,向右旋转。最后结果如下
旋转后,1,2,3的平衡因子变为0(无需记忆)。再次发表个人意见,平衡因子要用到的时候推一下就好了。
2. 右子树过高
a) RR型
还是引用一下之前的例子。旋转的步骤如下。旋转之后为“原来根结点的右孩子作为新的根结点”。
i. 将2作为根结点
ii. 将1作为2的左孩子
iii. 将2的左孩子作为1的右孩子(维护树的有序性,只是此处为NULL而已)
最后1,2,3的平衡因子都为EH。
b)RL型
还是引用一下之前的例子。与LR型类似,我们需要进行两次旋转。旋转之后为“原来根结点的右孩子的左孩子作为新的根结点”。
第一,以根结点的右孩子即3为中心向右旋转,结果如下。具体步骤如下
i. 将2作为1的右孩子
ii. 将3作为2的右孩子
iii. 将2的右孩子作为3的左孩子(维护树的有序性,只是此处为NULL而已)
第二,以原根结点即1,作为中心,向左旋转。结果如下。具体步骤如下
i. 将2作为根结点
ii. 将1作为2的左孩子
iii. 将2的左孩子作为1的右孩子(维护树的有序性,只是此处为NULL而已)
最后1,2,3的平衡因子都会EH
3、 插入时失衡与调整的总结
- 在所有的不平衡情况中,都是按照“寻找最小不平衡树”->“寻找所属的不平衡类别”->“根据4种类别进行固定化程序的操作”。
- LL,LR,RR,RL其实已经为我们提供了最后哪个结点作为新的根指明了方向。如LR型最后的根结点为原来的根的左孩子的右孩子,RL型最后的根结点为原来的根的右孩子的左孩子。我们只要记住这四种情况,可以很快地推导出所有的情况。
- 维护平衡二叉树,最麻烦的地方在于平衡因子的维护。想要熟悉这个过程,建议读者多多画图,在感官上首先体验这个过程。
说到这里,我们已经了解了了解了什么是平衡二叉树,插入结点后如何调整平衡二叉树。我们数据结构中经常讲到的有增删查改,那么下面我们来讲解一下如何删除。
AVL树的删除时的失衡与调整
今天心血来潮想要写这篇博客的原因主要就在于此。我在网上找了许久,很多人对于AVL树的查找,插入都讲解得非常精彩,但是删除的时候却经常贴出一段代码,比较少有讲解,对于我等需要完成作业的学生实在难受,完成作业后就希望能够与大家分享一下。咳咳,我们回归正题。前方高能,喝口水,看一下窗外帅哥美女再继续看吧。
一、 预备知识
1. 树的删除
假如有一棵二叉查找树如下,我们对它进行中序遍历,可以得到1, 2, 2.5, 3。我们发现,这是一个递增的序列。假如我们现在要删除的结点为3,在不考虑树的平衡问题时,应该哪个结点来作为顶替3的位置呢呢?答案是:对排序二叉树进行中序遍历时,3的直接前驱或者直接后驱。在这里,就是2.5,所以删除后,不进行调整的结果如中间图。假如我们现在要删除的结点为2,在不考虑树的平衡问题时,1顶替2的位置(假设左孩子优先于右孩子)。最后如下右图。
具体的步骤如下:
i. 寻找到要删除的结点(3)
ii. 将待删除结点的直接前驱或者直接后驱赋值给待删除结点(2.5赋值给3结点)
iii. 将直接前驱或者直接后驱删除(将叶子结点的2.5删除)
由于我们今天主要讲的是平衡二叉树的平衡调整,所以这部分就权当给读者恶补一下。假如读者还是不能理解,请先查看一下二叉查找树的删除,再继续往下看。
2. 平衡因子的预告
我们已经知道,平衡因子有且仅有三种取值,LH,RH,EH。对于如下的一棵树,删除一个结点后
a) 原本树左右子树等高。根平衡因子的取值变化为EH->LH,EH->RH。
b) 原本树左右子树不等高,在较高的子树上进行删除,根平衡因子的取值变化为LH->EH,RH->EH。需要注意的是,当根的平衡因子变化为LH->EH,RH->EH时整棵树的高度是下降的。最简单的例子如下。以下两棵树,分别删除1,3后,平衡因子LH->EH,RH->EH。最后树的高度都下降了。
c) 原本树左右子树不等高,在较低的子树上进行删除,此时需要对树进行平衡处理。如下删除了结点1,得到右边的不平衡树。
3. 什么会导致树高降低
a) 如第2点的的第b项,根的平衡因子由LH->EH,RH->EH时整棵树的高度是下降的。
b) 建立在a点以及平衡处理正确的基础上,对树进行正确的平衡处理后,树高会降低。为什么呢?因为其实最小不平衡子树进行旋转后,最小不平衡子树根的平衡因子总是变
为EH,或者说,平衡调整总是降低了最小不平衡子树的高度。举例如下。树的高度由原来的3变为了2.
二、 正式进入AVL树的删除与调整
1. 删除结点导致平衡二叉树失衡
AVL树也是一棵二叉查找树,所以它的删除也是建立在二叉查找树的删除之上的,只是,我们需要在不平衡的时候进行调整。而我们在预备知识的第2点中的C项中已经提及到,假如我们在较低的子树上进行删除,将会直接导致不平衡树的出现。那么,我们需要进行平衡处理的,就在于此种情况。举个栗子。
2. 调整不平衡子树后,导致了更大的不平衡子树
假设最小不平衡子树为A,它为双亲结点b的左子树,而b的平衡因子为RH。假设我们现在对A进行了平衡处理,如上所讲,进行平衡处理将导致树高降低。即我们让b较矮的子树变得更矮了。此时对于b而言,同样也是不平衡的。此时,我们需要再一次进行一次平衡处理。举个栗子如下。
假设我们删除了结点6.那么最小不平衡子树就是1,3,5对应的二叉树。它的双亲10的平衡因子为RH。我们首先对最小不平衡子树进行调整,结果如右图。我们发现,最小不平衡子树从根结点的左子树变成了整棵树,所以这个时候我们又要进行一次平衡调整。具体的平衡调整步骤与插入时是一致的,在这里就赘述。
在讲解插入新的结点进行平衡时,说到删除时与插入时不有着很大的不同就在于此。插入时,进行一次平衡处理,整棵树都会处于平衡状态,而在删除时,需要进行多次平衡处理,才能保证树处于平衡状态。
细心的朋友可能发现,上面右图中,最小不平衡子树的较高子树的平衡因子为EH。这个时候,就出现了前面插入时提及的不可能出现的失衡情况。
3. 失衡与调整的最后一种情况LE与RE
LE与RE型的失衡树,在进行调整的时候,和LL与RR型的旋转方式是一致的。只是最后初始根结点的平衡因子不为EH而已。就拿上面的例子而言,调整后的结果如下。初始根结点的平衡因子为RH。相对应的,假如是LE的情况,调整后初始根结点的平衡因子为LH。
图解:平衡二叉树,AVL树的更多相关文章
- 二叉查找树(BST)、平衡二叉树(AVL树)(只有插入说明)
二叉查找树(BST).平衡二叉树(AVL树)(只有插入说明) 二叉查找树(BST) 特殊的二叉树,又称为排序二叉树.二叉搜索树.二叉排序树. 二叉查找树实际上是数据域有序的二叉树,即对树上的每个结点, ...
- Java 树结构实际应用 四(平衡二叉树/AVL树)
平衡二叉树(AVL 树) 1 看一个案例(说明二叉排序树可能的问题) 给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在. 左边 BST 存在的问题分析: ...
- 平衡二叉树,AVL树之图解篇
学习过了二叉查找树,想必大家有遇到一个问题.例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况.有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本.而只有建 ...
- 【数据结构】平衡二叉树—AVL树
(百度百科)在计算机科学中,AVL树是最先发明的自平衡二叉查找树.在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树.查找.插入和删除在平均和最坏情况下都是O(log n).增 ...
- 二叉查找树(BST)、平衡二叉树(AVL树)
二叉查找树(BST) 特殊的二叉树,又称为排序二叉树.二叉搜索树.二叉排序树. 二叉查找树实际上是数据域有序的二叉树,即对树上的每个结点,都满足其左子树上所有结点的数据域均小于或等于根结点的数据域,右 ...
- 平衡二叉树,AVL树之代码篇
看完了第一篇博客,相信大家对于平衡二叉树的插入调整以及删除调整已经有了一定的了解,下面,我们开始介绍代码部分. 首先,再次提一下使用的结构定义 typedef char KeyType; //关键字 ...
- 大话数据结构—平衡二叉树(AVL树)
平衡二叉树(Self-Balancing Binary Search Tree/Height-Balanced Binary Search Tree),是一种二叉排序树,当中每个节点的左子树和右子树的 ...
- 平衡二叉树-AVL树(LL、RR、LR、RL旋转)
平衡二叉树的定义: 任意的左右子树高度差的绝对值不超过1,将这样的二叉树称为平衡二叉树,二叉平衡树前提是一个二叉排序树. 平衡二叉树的插入: 二叉平衡树在插入或删除一个结点时,先检查该操作是否导致了树 ...
- 数据结构之平衡二叉树(AVL树)
平衡二叉树(AVL树)定义如下:平衡二叉树或者是一棵空树,或者是具有以下性质的二叉排序树: (1)它的左子树和右子树的高度之差绝对值不超过1: (2)它的左子树和右子树都是平衡二叉树. AVL树避免了 ...
随机推荐
- HTML学习第二天
HTML学习第二天 今天学的比较少,有些乱,先只写一个知识点 三种样式表插入方式 外部样式表: <link rel="stylesheet" type="text/ ...
- UVALive - 7752 Free Figurines
题意:有n个娃娃,如果大娃娃j直接套小娃娃i,则fa[i] = j.若fa[i] = 0,则该娃娃自由.给出每个娃娃初始的父亲,和改变后的父亲,在满足以下合法操作的条件下,问最少需要多少次变换. 1. ...
- JavaScript原生Ajax请求纯文本数据
源代码 ajax1.html: <!DOCTYPE html> <html> <head> <meta charset="utf-8"&g ...
- 公司网络能ping通ip,不能ping域名
第一天去公司,就因为网络问题弄了大半天.只能ping通ip,不能ping域名.网上查了各种方式都不行,刷新dns缓存.说是什么域名解析问题.dns的问题.公司里好几个人都看了看不能解决.领导让重装系统 ...
- 3-Java逻辑控制语句
目录 Java选择结构 Java循环结构 return.break.continue Math.random()的使用和条件运算符 1.Java选择结构 1.1.if(布尔表达式)单选结构 - 当布尔 ...
- 逆向--C函数和汇编
C函数和汇编 C代码 (编译工具gcc (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609平台ubuntu i386 32位) int bar(int c ...
- SciKit-Learn 加载数据集
章节 SciKit-Learn 加载数据集 SciKit-Learn 数据集基本信息 SciKit-Learn 使用matplotlib可视化数据 SciKit-Learn 可视化数据:主成分分析(P ...
- MBE风格图标
图标的定义 图标是一种通过相似性或类比行来代表对象的符号. 图标的重要性 1.图形以其无与伦比的识别性带来信息传达效率的提升. 2.图形以其百变多样的趣味性带来视觉体验的享受提成用户体验. 3.图形以 ...
- jQuery文档加载事件
$(document).ready(handler) $().ready(handler) (this is not recommended) $(handler) 相当于: $(document). ...
- 【pwnable.kr】coin1
pwnable从入门到放弃又一发 说是一道pwnable,其实是一道coding... nc pwnable.kr 9007 连接上看看,玩硬币? 老子是来拿flag的,谁来哄孩子来了!!! 算了,f ...