C++(数据结构与算法):67---平衡搜索树之AVL树(AVL Tree)

一、AVL树概述

  • 如果搜索树的高度总是O(logn),就能保证查找、插入、删除的时间为O(logn)。最坏情况下的高度为O(logn)的树称为平衡树
  • 比较流行的一种平衡树是AVL树,它是Adelson-Velskii和Landis在1962年提出的

AVL树的定义

  • 一棵空的树是AVL树
  • 如果T是一棵非空的二叉树,T(L)和T(R)分别是其左子树和右子树,那么当T满足以下条件时,T是一棵AVL树
    • 1.T(L)和T(R)是AVL树
    • 2.|h(L)-h(R)|<=1,其中h(L)和h(R)分别是T(L)和T(R)的高

AVL搜索树

  • 一棵AVL搜索树即是二叉搜索树,也是AVL树
  • 例如:
    • 下面a和b都是AVL树,但是c不是AVL树
    • 其中a不是AVL搜索树,因为其不是二叉搜索树。b是AVL搜索树

C++(数据结构与算法):67---平衡搜索树之AVL树(AVL Tree)_第1张图片

索引AVL搜索树

  • 一棵索引AVL搜索树既是索引二叉搜索树,也是AVL树
  • 下图中的a和b都是索引AVL搜索树

C++(数据结构与算法):67---平衡搜索树之AVL树(AVL Tree)_第2张图片

AVL树的特征

  • 如果用AVL搜索树来描述字典,并在对数级时间内完成每一种字典操作,那么AVL树有以下特征:
    • 1.一棵n个元素的AVL树,其高度是O(logn)
    • 2.对于每一个n,n>=0,都存在一棵AVL树
    • 3.对一棵n元素的AVL搜索树,在O(高度)=O(logn)的时间内可以完成查找
    • 4.将一个新元素插入一棵n元素的AVL搜索树中,可以得到一棵n+1个元素的AVL树,而且插入用时为O(logn)
    • 5.一个元素从一棵n元素的AVL搜索树中删除,可以得到一棵n-1个元素的AVL树,而且删除用时为O(logn)
  • 特征2可以从特征4中推出,因此可以省略。特征1、3、4、5在下面证实

二、AVL树的高度

C++(数据结构与算法):67---平衡搜索树之AVL树(AVL Tree)_第3张图片

三、AVL树的描述

  • AVL树一般用链表描述
  • 为了简化插入和删除操作,我们为每个节点增加一个平衡因此bf。节点x的平衡因子bf(x)定义为:
bf(x) = x的左子树高度-x的右子树高度
  • 从AVL树的定义可以知道,平衡因子的可能取值为-1、0、1
  • 下图是两棵带有平衡因子的AVL搜索树:

C++(数据结构与算法):67---平衡搜索树之AVL树(AVL Tree)_第4张图片

四、AVL搜索树的搜索

  • AVL搜索树的查找与前面介绍的二叉搜索树的查找一样,步骤如下:
    • 1.从根开始,如果根节点为空,就停止搜索;如果不为空,进行第2步
    • 2.将要查找的元素与根节点进行比较,如果比根节点值小,就向左孩子查找;如果比根节点值大,就向右孩子查找
    • 3.重复2步骤,只要比某个节点值小就向左孩子查找;只要比某个节点值大,就向右孩子查找
    • 4.查找到之后返回查找的节点,如果没查找就返回空
  • 因为n元素的AVL数的高度为O(logn),所以搜索时间为O(logn)

五、AVL搜索树的插入

插入导致AVL的不平衡

  • 如果使用前面二叉搜索树的插入方法应用于AVL搜索树,那么在插入之后可能会导致AVL失去平衡
  • 例如,把关键字32插入到下图a的AVL树中,结果如下图b所示,其中有的节点的平衡因子不是-1、0、1,因此它不是AVL树,此时搜索树就是不平衡的了

C++(数据结构与算法):67---平衡搜索树之AVL树(AVL Tree)_第5张图片

  • 通过移动不平衡树的子树可以恢复平衡,例如将上图b移动之后,重新形成的平衡树如下所示:

C++(数据结构与算法):67---平衡搜索树之AVL树(AVL Tree)_第6张图片

插入操作导致不平衡的几种情形:

  • ①在不平衡树中,平衡因子的值限于-2、-1、0、1、2
  • ②平衡因子为2的节点在插入前的平衡因子为1。类似的,平衡因子为-2的节点在插入前的平衡因子为-1
  • ③只有从根到插入节点的路径上的节点,其平衡因子在插入后会改变
  • ④假设A是离新插入节点最近的祖先,且平衡因子是-2或2(在上图b中,A是40那个节点),在插入前,从A到新插入节点的路径上,所有节点的平衡因子都是0

X节点的概念

  • 当我们从根节点往下移动寻找插入新元素的位置时,能够确定上面④中的A。从②可以知道,bf(A)在插入前的值既可以是-1,也可以是1。设X是最后一个具有这种平衡因子的节点
  • 例如:
    • 当把32插入下图a所示的AVL树中时,X是关键字为40的节点
    • 当把28或50插入下图b所示的AVL树中时,X是关键字为25的节点
    • 当把10、或14、或16、或19插入下图b所示的AVL树中时,X不存在

C++(数据结构与算法):67---平衡搜索树之AVL树(AVL Tree)_第7张图片

如果X节点不存在

  • 如果上面介绍的X节点不存在,那么从根节点到新插入节点的途径中,所有节点在插入前的平衡因子都是0
  • 由于插入操作只会使平衡因子增减0或1,并且只有从根节点至新插入节点的途径中的节点的平衡因子会改变,因此插入之后,树的平衡不会被破坏。因此,只有插入后的树是不平衡的,X节点才存在
  • 如果插入后bf(X)=0,那么以X为根节点的子树的高度在插入前后是相同的。例如,如果插入前的高度是h,且bf(X)=1,那么:
    • X的左子树高度是h-1,右子树高度是h-2(如下图a所示)
    • 为了使平衡因子为0,必须在中做插入,得到高度为h-1的新子树(如下图b所示)
    • 由于从X到新插入节点的路径中的所有节点在插入前的平衡因子均为0,所以的高度必须增加到h-1
    • X的高度仍然保持为h,X的祖先的平衡因子在插入前后保持相同,这样,树的平衡被保持住了

C++(数据结构与算法):67---平衡搜索树之AVL树(AVL Tree)_第8张图片

L型不平衡、R型不平衡

  • 节点A的不平衡情况有两类:L型不平衡(新插入节点在A的左子树中)、R型不平衡(新插入节点在A的右子树中)
  • 根据A的孙节点情况,A的不平衡状态还可以细分:
    • LL(新插入的节点在A节点的左子树的左子树中)
    • LR(新插入的节点在A节点的左子树的右子树中)
    • RL(新插入的节点在A节点的右子树的左子树中)
    • RR(新插入的节点在A节点的右子树的右子树中)
  • 例如下图就是一种普通的LL型不平衡插入情况:
    • 图a是插入前的条件
    • 图b是在节点B的左子树插入一个元素后的情形

C++(数据结构与算法):67---平衡搜索树之AVL树(AVL Tree)_第9张图片

  • 例如下图就是一种普通的LR型不平衡插入情况:
    • 图a是插入前的条件
    • 图b是在节点B的右子树插入一个元素后的情形

C++(数据结构与算法):67---平衡搜索树之AVL树(AVL Tree)_第10张图片

不平衡调整(单旋转、双旋转)

  • 矫正LL和RR型不平衡所做的转换称为“单旋转”
  • 矫正LR和RL型不平衡所做的转换称为“双旋转”
    • LR型不平衡所做的双旋转可以看做RR旋转加LL旋转
    • RL型不平衡所做的双旋转可以看做LL旋转加RR旋转
  • 例如,下面就是对于一种LL型不平衡所做的单旋转:
    • 原来以A为根节点的子树,现在以B为根节点
    • 仍然是B的左子树,A变成B的右子树的根,变成A的左子树,A的右子树不变
    • 随着A的平衡因子的改变,在从B到新插入节点的路径上,的所有节点的平衡因子都要改变。其他节点的平衡因子与旋转前的一致
    • 因为图a和图c的子树的高度是一样的,所以它们的祖父节点如果存在的话,其平衡因子与插入前是一样的。因此所有节点的平衡因子都是-1、0、1

C++(数据结构与算法):67---平衡搜索树之AVL树(AVL Tree)_第11张图片

  • 例如,下面就是对于一种LR型不平衡所做的双旋转:
    • 假设C是新插入的节点,插入之后导致不平衡了(假设C的左右子树为,但是也可能为空)
    • 将C作为新根节点,原根节点作为C的右子树
    • 将C左子树(如果有的话)作为原C父节点的右子树;将C的右子树(如果有的话)作为原根节点A的右子树

C++(数据结构与算法):67---平衡搜索树之AVL树(AVL Tree)_第12张图片

AVL搜索树的插入步骤总结

  • 根据上面的概述,下面的步骤可以细化为C++代码,其复杂度为O(高度)=O(\log n):
    • ①沿着从根节点开始的路径,根据新元素的关键字,去寻找新元素的插入位置。在此过程中,记录最新发现平衡因子为-1或1的节点,并令其为A节点。如果找到了具有相同关键字的元素,那么插入失败,终止算法
    • ②如果在步骤①中所描述的节点A不存在,那么从根节点开始沿着原路径修改平衡因子,然后终止算法
    • ③如果bf(A)=1并且新节点插入A的右子树中,或者bf(A)=-1并且新节点插入到左子树,那么A的平衡因子是0。在这种情况下,修改从A到新节点图中的平衡因子,然后终止算法
    • ④确定A的不平衡类型并执行相应的旋转,并对新子树根节点至新插入节点的路径上的节点的其平衡因子做相应的修改

六、AVL搜索树的删除

  • 其实AVL搜索树的删除处理与AVL树的插入处理原理是相同的
  • 如果AVL搜索树删除之后导致不平衡,可以利用AVL搜索树插入之后的不平衡方法来解决

单旋转

  • 例如下面跟A的bf(A)=1,代表左右子树高度差为1
  • 如果此时删除了A的右子树,并且导致bf(A)变为2,此时需要单旋转来保持平衡
  • 单旋转与上面介绍的AVL树的LL单旋转一样(见上):
    • 将原根A的左子树B作为新根节点
    • 将A作为B的右子树
    • 将B的右子树传递给A,作为A的左子树
    • B的左子树还是B的左子树
    • 最终如下图所示

C++(数据结构与算法):67---平衡搜索树之AVL树(AVL Tree)_第13张图片

双旋转

  • 例如下面跟A先开始bf(A)=1,删除A的右子树导致不平衡,此时需要进行双旋转
  • 双旋转与与上面介绍的AVL树的LR型不平衡一样(见上):
    • 将B的右子树C作为新根,将B作为C的左子树,将A作为C的右子树
    • 将C的原右子树作为A的左子树,将C的原左子树作为B的右子树
    • 其余不变,最终如下图所示

C++(数据结构与算法):67---平衡搜索树之AVL树(AVL Tree)_第14张图片

你可能感兴趣的:(C++(数据结构与算法))