0
点赞
收藏
分享

微信扫一扫

STM32F407+FreeRTOS+LWIP UDP组播

奔跑的酆 04-10 10:00 阅读 0
c++算法

AVL树

1. AVL树的概念

下面我们通过探究AVL树的特点来了解它。

2. AVL树的特点

我们将AVL树的名字拆开,很容易就知道它到底是一棵什么样的树:自平衡的、二叉树、搜索树,所以它的特点有:

  1. 搜索树:任意一棵子树,它的左子树的所有节点都比根节点小,且右子树的所有节点都比根节点大;在搜索树中,我们有K型搜索树和K/V型搜索树,AVL树同样有这两种。
  2. 自平衡的:任意一棵子树,左子树和右子树的高度差不超过1,一旦超过1,就会自行进行树旋转操作,使得这棵AVL树的左右子树高度差不超过1。
  3. 二叉树:AVL树的数据结构中有三个指针,分别指向左孩子、右孩子、父节点;此外,部分AVL树的实现中,存在一个整形变量平衡因子,用以辅助实现自平衡功能(关于平衡因子,后文会进行解释)。
  4. 时间复杂度:查找、遍历、插入、删除都为O(log2N)。

在AVL树众多特点中,我们最最需要关注的是:AVL树是如何实现自平衡的?下面,我们就来揭开AVL树自平衡的神秘面纱。

3. AVL树的自平衡

AVL树如何实现自平衡呢?
实现自平衡,涉及到两个概念,一个是平衡因子,另一个是旋转,平衡因子用来记录AVL树是否平衡,旋转则是让不平衡的AVL树变得平衡。

3.1 平衡因子
  • 平衡因子( b a l a n c e f a c t o r ,简称 b f ) = 右子树的高度 − 左子树的高度 平衡因子(balance factor,简称bf)= 右子树的高度 - 左子树的高度 平衡因子(balancefactor,简称bf=右子树的高度左子树的高度
    当然,反过来也是可以的,甚至不使用平衡因子也是可以的,我们为了理解方便,以下所有例子都是平衡因子=右子树的高度-左子树的高度。

在这里插入图片描述

  • 对于一棵AVL树,任何一个节点的平衡因子的可能取值为{-2,-1,0,1,2}

  • 当插入一个新的节点,它的祖先节点的平衡因子可能会受到影响,因此我们需要一路向上更新祖先节点的平衡因子。

  • 以下是插入了一个新的节点后,更新平衡因子的思路:

    1)如果当前节点是它的父节点parent的左孩子,说明新节点插入到了parent的左子树,左子树的高度加了一,父节点的平衡因子减一;
    2)如果当前节点是它的父节点parent的右孩子,说明新节点插入到了parent的右子树,右子树的高度加了一,父节点的平衡因子加一;
    3)若当前 b f = 0 bf = 0 bf=0,表示左右子树一样高,处于平衡状态,说明再往上的祖先节点们都没有受到影响,更新结束。(说明:因为AVL树每次只能插入一个节点,若更新完平衡因子后,节点X的bf变成了0,说明节点X原来的bf不是-1就是1,也就是说原本节点X的左右子树高度相差1,在插入新节点后bf变成了0,也就是给左右子树中矮的那棵子树增高了1层,就使得节点X的左右子树一样高了。对于X节点和X的祖先来说,插入了一个新节点,并没有让自己左右子树的高度差变得更大,所以也就没有必要再往上更新平衡因子了。)
    4)若当前 b f = ± 1 bf = \pm1 bf=±1,表示左右子树的高度差为1(虽然不是绝对的平衡,但是满足AVL树的要求),不必进行旋转操作,需要继续向上更新平衡因子(把当前节点cur的parent作为当前节点,parent的parent作为新的parent);
    5)更新完平衡因子后,若当前 b f = ± 2 bf = \pm 2 bf=±2,表示左右子树的高度差为2,此时处于不平衡状态,需要进行旋转操作,使其平衡,旋转后更新结束。
    6)更新到根节点时,更新结束。

  • 值得注意的是,曾经我把平衡因子的计算方法记错了,以为 当前的 b f = 右孩子的 b f − 左孩子的 b f 当前的bf=右孩子的bf-左孩子的bf 当前的bf=右孩子的bf左孩子的bf,导致代码出现严重的问题。其实真正的计算方法是:
    平衡因子 = 右子树的高度 − 左子树的高度 平衡因子 = 右子树的高度 - 左子树的高度 平衡因子=右子树的高度左子树的高度

3.2 旋转

当一棵AVL树的某个节点的平衡因子变成了2或-2,就需要进行旋转操作,将左右子树的高度重新变回{-1, 0, 1}之间。

旋转分为两种,一种是单旋转,另一种是双旋转。

下面,我们以插入操作来讲解旋转!

3.2.1. 单旋转(Single Rotation)

单旋又分为左单旋和右单旋,它们分别适用于不同的场景:
在这里插入图片描述
在这里插入图片描述

1)左单旋

在这里插入图片描述

右边“重”一点,需要把整个图像向左旋转。
核心步骤:
parent->right = curleft;
cur->left = parent;

下面我们针对h取不同的值图解分析:

  1. h = 0
    h=0时,有且仅有1种场景!

    在这里插入图片描述

    注意:这里新节点只能插入到cur的右边!如果插入到cur的左边,那么就需要右左双旋了!

  2. h = 1
    新节点有2种插入位置,因此h=1时,有2种可能场景!

    在这里插入图片描述

    注意:这里新节点可以插入到“70”的左或右,新节点“80”节点与“70”可以看做一个整体,新节点在哪个位置不影响左单旋!

  3. h = 2
    a子树有3种,b子树有3种,c子树有1种;新节点插入的位置有4种,因此h=2时,共有36种可能场景!

    在这里插入图片描述

    为什么a子树和b子树可以是x、y、z当中任意一种,但是c子树只可能是z呢?
    原因:a子树和b子树是什么类型,对于parent而言没有任何影响,不会让parent的平衡因子变得更大,因此都可以;
    但是c子树不同,对于左单旋而言,新节点必然插入到c子树当中(为什么说必然,如果插入到a子树,AVL树依旧平衡,用不着旋转,如果插入到b子树,就是双旋了,我们后面再讲),因此:
    1)假设c子树是x型的
    在这里插入图片描述

    新节点有三种插入的位置,左边两个位置插入后会诱发双旋(双旋请看后面),右边一个位置插入后AVL树依旧平衡。

    2)假设c子树是y型的

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

新节点也有三种插入的位置,新节点插入在左边后AVL树依旧平衡。
插入在右边两个位置时,c子树本身就不平衡了,
当插入在最右的位置,c子树内部进行左单旋,使AVL树平衡,这属于"h=0",而不属于"h=2";
当插入在右起第二个位置,c子树内部进行右左双旋,AVL树平衡,这不属于左单旋。

3)只有c子树是z型的,parent节点的平衡因子才会变成2,才会需要进行左单旋。
  1. h取更大的值
    会有越来越多可能出现的场景。

所以需要用到左单旋的场景有无数种!

2)右单旋

同样地,右单旋与左单旋一样,仅仅是位置变化罢了。

在这里插入图片描述

左边“重”一点,需要把整个图像向右旋转。
核心步骤:
parent->left = curright;
cur->right = parent;

右单旋这里就不再根据不同的h值画图分析了,它与左单旋大同小异。
h=0时仅仅有1种场景;
h=1时有2种场景;
h=2时有36种场景;
h越来越大,可能的场景越来越多…
总体来说右单旋也有无数种可能场景!

3.2.2 双旋转(Double Rotation)

所谓双旋,其实就是旋转两次,一次左单旋,一次右单旋。
双旋转也分为两种:右左双旋和左右双旋。

1)右左双旋

先右单旋,后左单旋。
所有符合下图所示的AVL树结构,都会导致右左双旋!

在这里插入图片描述

新节点既可能插入到b子树中,也可能插入到c子树中,这两种情况都会导致右左单旋!

  1. h = 0
    h=0时,有且仅有这一种场景!

    在这里插入图片描述

  2. h = 1
    新节点有两个可能插入的位置,因此h=1引发右左双旋有2种可能场景!

    在这里插入图片描述

  3. h = 2

    a子树和d子树各有3种,b子树和c子树可能分别有2种,因此符合h=2的右左双旋共有36种可能场景! 在这里插入图片描述

    在这里插入图片描述

  4. h为更大的值,会有更多可能引发右左双旋的场景!

因此,双旋与单旋一样,都有无数种可能的场景!

2)左右双旋

先进行左单旋,再进行右单旋。
所有符合下图所示结构的AVL树,都会引发左右双旋!
在这里插入图片描述

和右左双旋一样,b子树和c子树都有可能插入新节点!

当h=0时,有且仅有1种场景;
当h=1时,有2种可能场景;
当h=2时,有36种可能场景;
当h更大,有更多种可能的场景!

因此,左右双旋也有无数种可能场景!

AVL树的删除操作,比插入操作更加复杂,它不仅仅要满足搜索树的条件(这本身就够复杂了),还要满足“平衡”,删除导致的平衡因子的判断更为复杂!能力有限,本文不对删除操作进行讲解。

4. AVL树源码(插入操作)

下面附上AVL树插入操作的源代码:

举报

相关推荐

0 条评论