平衡二叉树,AVL树之图解篇

  学习过了二叉查找树,想必大家有遇到一个问题。例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况。有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本。而只有建立的树如图2,才能够最大地体现二叉树的优点。

                         

  在上述的例子中,图2就是一棵平衡二叉树。科学家们提出平衡二叉树,就是为了让树的查找性能得到最大的体现(至少我是这样理解的,欢迎批评改正)。下面进入今天的正题,平衡二叉树。

AVL的定义

  平衡二叉查找树:简称平衡二叉树。由前苏联的数学家Adelse-Velskil和Landis在1962年提出的高度平衡的二叉树,根据科学家的英文名也称为AVL树。它具有如下几个性质:

  1. 可以是空树。
  2. 假如不是空树,任何一个结点的左子树与右子树都是平衡二叉树,并且高度之差的绝对值不超过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、  插入时失衡与调整的总结

  1. 在所有的不平衡情况中,都是按照“寻找最小不平衡树”->“寻找所属的不平衡类别”->“根据4种类别进行固定化程序的操作”。
  2. LL,LR,RR,RL其实已经为我们提供了最后哪个结点作为新的根指明了方向。如LR型最后的根结点为原来的根的左孩子的右孩子,RL型最后的根结点为原来的根的右孩子的左孩子。我们只要记住这四种情况,可以很快地推导出所有的情况。
  3. 维护平衡二叉树,最麻烦的地方在于平衡因子的维护。想要熟悉这个过程,建议读者多多画图,在感官上首先体验这个过程。

 

  说到这里,我们已经了解了了解了什么是平衡二叉树,插入结点后如何调整平衡二叉树。我们数据结构中经常讲到的有增删查改,那么下面我们来讲解一下如何删除。

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。

          

  假如你看到了这个地方,请先为疲惫不堪的自己鼓鼓掌。本篇就到此结束,下一篇将为大家带来具体的C代码实现。另外,这是followDreamLgx第一次写博客,希望大家批评改正。

转载于:https://www.cnblogs.com/suimeng/p/4560056.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/375901.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

盒子模型的总结

转载于:https://www.cnblogs.com/zy2012/p/3725677.html

maven generating project in batch mode hang

现象: 执行 archetype:generate 的时候,会产生[INFO] Generating project in Batch mode原因是:网速问题, 解决方法: 设置maven不要从远程服务器上获取catalog,增加参数-DarchetypeCataloginternal 如何在i…

微博 Android 启动广告,使用Xposed去除微博国际版的启动广告

本文同步更新于旺仔的个人博客,访问可能有点慢,多刷新几次。前面有篇文章已经介绍了如何创建Xposed模块的文章了,这篇就让我们来实现一个简单的去除启动广告的功能吧。起因为什么要是要去掉微博国际版的开屏广告呢,因为广告烦人啊…

linux命令:vim文件操作命令、新建用户,查看用户列表,chown命令

命令 简单说明 :w 保存编辑后的文件内容,但不退出vim编辑器。这个命令的作用是把内存缓冲区中的数据写到启动vim时指定的文件中。 :w! 强制写文件,即强制覆盖原有文件。如果原有文件的访问权限不允许写入文件,例如,原有的文件…

cocos2d-x android 环境搭配,cocos2d-x Android环境配置问题和解决方法

1.前提:下载安装Cygwin,并已经在cygwin\home\admin(计算机用户名)下的.bash_profile中完成如下配置:NDK_ROOT /cygdrive/d/cocos2dxdev/andrid-ndk-r8e//NDK安装位置export NDK_ROOT问题:运行cygwin.exe.录入如下的第一行数据后,没…

栈的应用--括号匹配的检验

算法中设置一个栈,每次读入一个括号,若是右括号,则或者与置于栈顶的括号匹配,或者是不合法的情况,若是左括号,则入栈。若算法结束,栈是空的,则括号合法。 括号匹配函数 Status bra…

node.js 初体验

node.js 初体验 2011-10-31 22:56 by 聂微东, 174545 阅读, 118 评论, 收藏, 编辑 PS: ~ 此篇文章的进阶内容在为《Nodejs初阶之express》 ~ 2014/09/24 更新《Express 4.X 启航指南》 欢迎阅读和评论:) 最近写的文章收到许多朋友的反馈,感谢大家的支持和建议&#…

Linux内核3.0移植并基于Initramfs根文件系统启动

Linux内核移植与启动 Target borad:FL2440 Bootloader:U-boot-2010.09 交叉编译器:buildroot-2012.08 1.linux内核基础知识 首先,磨刀不误砍柴工。在动手进行linux内核移植之前,我们有必要对linux内核进行一定的了解。…

操作系统上机作业--实现shell(2)(多进程)

sh2.c: 实现shell程序,要求在第1版的基础上,添加如下功能 • 实现文件重定向 • $ echo hello >log • $ cat log • Hello 实现思路: 和sh1.c相比,主要是修改了cmd函数的实现过程。通过循环找出重定向符号"&g…

操作系统上机作业--根据莱布尼兹级数计算PI(1)(多线程)

pi1.c: 使用2个线程根据莱布尼兹级数计算PI • 莱布尼兹级数公式: 1 - 1/3 1/5 - 1/7 1/9 - ... PI/4 • 主线程创建1个辅助线程 • 主线程计算级数的前半部分 • 辅助线程计算级数的后半部分 • 主线程等待辅助线程运行結束后,将前半部分和后半部分相加实现思路&#xff1…

操作系统上机作业--根据莱布尼兹级数计算PI(2)(多线程)

pi2.c: 使用N个线程根据莱布尼兹级数计算PI • 与上一题类似,但本题更加通用化,能适应N个核心,需要使用线程参数来实现 • 主线程创建N个辅助线程 • 每个辅助线程计算一部分任务,并将结果返回 • 主线程等待N个辅助线程…

数组以及冒泡排序

数组 1、概念:可以帮我一次声明多个同类型的变量,这些变量再内存中是连续存储的。 2、声明语法:数据类型[] 数组名 new 数据类型[数组长度] 数组长度:一次要声明的同类型的变量个数。是在定义这个数组的时候就确定了&#xf…

操作系统上机作业--多线程排序

sort.c: 多线程排序 • 主线程创建一个辅助线程 • 主线程使用选择排序算法对数组的前半部分排序 • 辅助线程使用选择排序算法对数组的后半部分排序 • 主线程等待辅助线程运行結束后,使用归并排序算法归并数组的前半部分和后半部分 实现思路: ARRAY_CO…

jdk5下载链接

查看jdk版本 java -versionJDK下载 最新版本http://www.oracle.com/technetwork/java/javase/downloads/index.htmlJDK下载 版本1.5.22http://www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-javase5-419410.html#jdk-1.5.0_22-oth-JPR JDK…

html的细节优化,网站页面优化细节详解

原标题:网站页面优化细节详解SEO页面优化是继SEO结构优化之后,另一个重要优化地方;页面标题在每个页面中的关键位置,出现目标关键词,这是我们做页面优化的基础思路,关键词位置,都有哪些呢?第一个是关键位置…

操作系统上机作业--使用条件变量解决生产者、计算者、消费者问题(多线程)

pc1.c: 使用条件变量解决生产者、计算者、消费者问题 /* • 系统中有3个线程:生产者、计算者、消费者 • 系统中有2个容量为4的缓冲区:buffer1、buffer2 • 生产者生产a、b、c、‘d、e、f、g、h八个字符,放入到buffer1 • 计算者从b…

程序各个段text,data,bss,stack,heap

网上找了一堆资料学习一下,了解这些, 有助于规化程序结构,优化代码; 使用gcc编译出来的程序,用size可以查看程序结构和大小, 如 1: #size hello 2: Text data bss dec hex filename 3: 778 200 4 982 3D6 hello 所以一个可执行的程序文件,结构分三部分: .text 代码段,用来存…

操作系统上机作业-- 使用信号量解决生产者、计算者、消费者问题(多线程)

pc2.c: 使用信号量解决生产者、计算者、消费者问题 • 功能和前面的实验相同,使用信号量解决 实现思路: 生产者、计算者、消费者三者之间的关系和上一个编程任务一样,不一样的是,将互斥量、条件变量封装起来作为信号量,处理方…

Singleton 单件

模式分类 从目的来看: -创建型(Creational)模式:负责对象创建 -结构型(Structural)模式:处理类与对象间的组合 -行为型(Behavioral)模式:类与对象交互中的职责…

10个利用Eclipse调试Java的常见技巧

http://www.open-open.com/news/view/1ad9099 阅读目录 1. Conditional Breakpoint2. Exception Breakpoint3. Watch Point4. Evaluation (Display or Inspect or Watch)5. Change Variable Values6. Stop in Main7. Environment Variables8. Drop to Frame9. Step Filter10. S…