欢迎光临
我们一直在努力

【C++】 STL篇 AVL树的模拟实现

一,AVL树的概念:最早的自平衡二叉树

1.1什么是AVL树

AVL树是最先发明的自平衡二叉搜索树,AVL是一颗空树,或者具备下列性质的二叉搜索树:它的左右子树都是AVL树,且左右子树的高度差的绝对值不超过1。AVL树是一颗高度平衡搜索二叉树,通过控制高度差去控制平衡。
AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis是两个前苏联的科学家,他们在1962年的论文《An algorithm for the organization of information》 中发表了它。

1.2平衡因子的概念

AVL树实现这里我们引入一个平衡因子(balance factor)的概念,每个结点都有一个平衡因子,任何结点的平衡因子等于右子树的高度减去左子树的高度(也可以是左子树高度减去右子树高度)必须控制在1以内,也就是说任何结点的平衡因子等于0/1/-1,AVL树并不是必须要平衡因子,但是有了平衡因子可以更方便我们去进行观察和控制树是否平衡,就像一个疾病的症状一样,他只是其中一种实现方式

  • 为什么高度差不超过1

思考一下为什么AVL树是高度平衡搜索二叉树,要求高度差不超过1,而不是高度差是0呢?0不是更好的平衡吗?画画图分析我们发现,不是不想这样设计,而是有些情况是做不到高度差是0的。 比如一棵树是2个结点,4个结点等情况下,高度差最好就是1,无法做到高度差是0。

在这里插入图片描述

1.3AVL树的优势

AVL树整体结点数量和分布和完全二叉树类似,高度可以控制在 logN,那么增删查的效率也可以控制在 O(logN),相比二叉搜索树有了本质的提升。在这里插入图片描述

二、搭建总体框架

先看代码

//节点类(三叉链)
template<class K, class V>
struct AVLTreeNode
{
//构造节点
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
,_parent(nullptr)
, _bf(0)
{}

pair<K, V> _kv;
AVLTreeNode* _left;
AVLTreeNode* _right;
AVLTreeNode* _parent;//父节点
int _bf;//平衡因子
};
//AVL树类
template<class K,class V>
class AVLTree
{
typedef struct AVLTreeNode<K,V> Node;
public:
AVLTree()
:_root(nullptr)
{}

private:
Node* _root;
};

我们看看相比二叉搜索树,AVL树的结点结构多了什么?首先是平衡因子,我们上面讲过了。然后是parent指针,没错,AVL树最适合的结构不是二叉链而是三叉链。这是为了方便后面插入节点时修改父节点的平衡因子。

三、实现AVL树的插入:维护平衡因子

  • AVL 树插入的难点在于平衡因子的更新以及平衡因子非法时如何进行旋转

AVL 树的插入前面部分和二叉搜索树的插入很类似 , 比当前节点大就往右边走,小就往左边走,相等就插入失败,走到空位就插入,不过二叉搜索树插入的是键值 key,而我们这里插入的是键值对 pair<K, V>,所以比较的时候是 cur->_kv.first 和 kv.first 进行比较;同时,在链接节点时需要注意修改节点父节点指针的指向,因为 AVL 树的节点是三叉链结构;
 AVL树,他首先是一颗二叉搜索树,那么插入一个值就应该按二叉搜索树规则进行插入,注意一下插入后要多一步链接父亲结点

bool Insert(const K& key, const V& value)
{
//没有节点
if (_root == nullptr)
{
_root = new Node(key, value);
return true;
}

Node* parent = nullptr;
Node* cur = _root;
Node* newnode = new Node(key, value);
while (cur)
{
if (key > cur->_kv.first)
{
parent = cur;
cur=cur->_right;
}
else if(key < cur->_kv.first)
{
parent = cur;
cur=cur->_left;
}
//相等不处理
else
{
return false;
}
}
//到这里就要到应该插入的位置了
if (key > parent->_kv.first)
{
parent->_right = newnode;
newnode->_parent = parent;
return true;
}
else
{
parent->_left = newnode;
newnode->_parent = parent;
return true;
}
}

3.1、平衡因子的更新

新增结点以后,只会影响祖先结点的高度,也就是可能会影响部分祖先结点的平衡因子,所以更新从新增结点->根结点路径上的平衡因子,实际中最坏情况下要更新到根,有些情况更新到中间就可以停止了,具体情况我们下面再详细分析。
更新平衡因子的原则

  • 平衡因子 = 右子树高度 – 左子树高度(可以是左-右,但是一般都是右-左)
  • 只有当前结点子树高度变化才会影响当前结点平衡因子继而继续影响祖先
  • 插入结点,会增加高度所以:
    新增结点在 parent 的右子树,parent 的平衡因子 ++,新增结点在 parent 的左子树,parent 平衡因子 – –
  • parent 所在子树的高度是否变化决定了是否会继续往上更新。
  • 3.2、更新停止条件

    根据平衡因子的更新结果,有三种情况:

    • 情况一:更新后 parent 的平衡因子等于 0
      这种情况怎么来的? 必然是更新中 parent 的平衡因子从 -1->0 或者 +1->0。

    更新前平衡因子是+1/-1,说明更新前 parent 子树一边高一边低,新增的结点插入在低的那边,插入后 parent 所在的子树高度不变 ,不会影响 parent 的父亲结点的平衡因子,更新结束。

    防止没有听明白,我找个图给大家看看:在这里插入图片描述

    如图,结点(3)的平衡因子初始是1,插入后 parent 所在的子树高度变-1,会影响 parent 的父亲结点的平衡因子,更新他的祖先3节点变成0此时3节点的字数高度不变更新结束.
    在这里插入图片描述

    • 情况二:更新后 parent 的平衡因子等于 1 或 -1

    更新前更新中 parent 的平衡因子变化为 0->1 或者 0->-1。

    为什么不可能是从+2/-2更新到+1/-1? 因为插入新的结点才会引发更新,而插入新的结点的大前提是在插入之前,他就是一颗AVL树。而存在某一个结点的平衡因子是+2/-2,这不是一颗AVL树,我们需要处理。(可以用我们上面那个例子理解,0是一种健康状态,+1和-1是一种亚健康状态有点问题但是问题不大,+2和-2是生病了我们需要对其进行治疗(也就是调整) )

    说明更新前 parent 子树两边一样高,新增的插入结点后,parent 所在的子树一边高一边低,parent 所在的子树符合平衡要求,但是高度增加了 1,会影响 parent 的父亲结点的平衡因子,所以要继续向上更新。
    我们还是找一张图来解释:在这里插入图片描述
    如图,插入结点后,parent(6)从0->1,所在子树高度发生变化,继续向上更新;parent(3)从0->1,所在子树高度发生变化,继续向上更新,以此类推直到更新到根节点,再不可向上继续更新,更新方才结束。在这里插入图片描述

    • 情况三:更新后 parent 的平衡因子等于 2 或 -2
      更新前更新中 parent 的平衡因子变化为 1->2 或者 -1->-2,说明更新前 parent 子树一边高一边低。新增的插入结点在高的那边,parent 所在的子树高的那边更高了,破坏了平衡。平衡因子异常
      在这里插入图片描述

    如上图,parent(14)平衡因子从0->-1,所在子树高度发生变化,继续向上调整,parent(10)平衡因子从1->2,平衡因子出现异常,这个时候就需要调整了。在这里插入图片描述

    parent 所在的子树不符合平衡要求,需要旋转处理,旋转的目标有两个:

    1、 把 parent 子树旋转平衡。
    2、 降低 parent 子树的高度,恢复到插入结点以前的高度。所以旋转后也不需要继续往上更新,插入结束。

    3.3、更新平衡因子代码

    首先第一步: 更新平衡因子:如果插入的结点在父亲结点的左边,就- -,在右边就++。
    然后第二步 :根据更新后的平衡因子做出判断(符合我们上面说的思路)

    //cur更好表示
    cur = newnode;
    //更新平衡因子代码
    //因为只会影响父亲开始的祖父节点所以就先从父亲开始了
    //只要他变成0了就不会影响再上去的节点的
    while (parent)
    {
    //左边插入——
    if (parent->_left == cur)
    parent->_bf;
    //右边插入++
    else
    parent->_bf++;
    //只要他变成0了就不会影响再上去的节点的更新结束
    if (parent->_bf == 0)
    break;
    //bf为-1||1 说明是从0变的还要继续往上更新直到变成0或者根节点的父节点为空无法进入循环
    if (abs(parent->_bf)==1)
    {
    cur = parent;
    parent = parent->_parent;
    }
    //bf为-2||2 说明是从1,-1变的这个时候不平衡了
    else if (abs(parent->_bf) == 2)
    {
    //旋转处理
    }
    }

    四、旋转逻辑:AVL树的四种旋转

    4.1、旋转的分类

    在上面我们了解到,在更新完平衡因子后由+1/-1->+2/-2时,AVL树不再平衡,这个时候就需要对不平衡结点进行旋转。
    旋转有两大重要原则,只有满足这两大原则,旋转才是有意义的:

    保持搜索树的规则
    让旋转的树从不平衡变平衡,其次降低旋转树的高度

    旋转总共分为四种,左单旋/右单旋/左右双旋/右左双旋。接下来我们一一讲解。(说明:下面的图中,有些结点我们给的是具体值,如10和5等结点,这里是为了方便讲解,实际中是什么值都可以,只要大小关系符合搜索树的性质即可。)

    4.2、右单旋原理:从抽象到具体详解

    4.2.1、发生右单旋的抽象场景

    如下图,展示的是10为根的树,有a/b/c抽象为三棵高度为h的子树(h>=0),该树有如下前提:

    a/b/c均符合AVL树的要求。
    10可能是整棵树的根,也可能是一个整棵树中局部的子树的根。

    这里a/b/c是高度为h的子树,是一种概括抽象表示,它代表了所有右单旋的场景,实际右单旋形态有很多种。我们后面会详细分析。在这里插入图片描述
    然后,我们在a子树中插入一个新结点,导致a子树的高度从h变成h+1,不断向上更新平衡因子,导致10的平衡因子从-1变成-2,10为根的树左右高度差超过1,违反平衡规则。10为根的树左边太高了,需要往右边旋转。在这里插入图片描述

    4.2.2、如何进行右单旋

    旋转核心步骤:

  • 因为5 < b子树根的值 < 10,将b变成10的左子树。
  • 5要转上去,所以10变成5的右子树。
  • 5变成这棵树新的根。
  • 完成这三步之后,①符合搜索树的规则,②控制了平衡,③以10为根的这棵的高度恢复到了插入之前的h+2(左右高度差抹平,如图),符合旋转原则。如果插入之前10是整棵树的一个局部子树,旋转后不会再影响上一层,插入结束了。在这里插入图片描述

    4.2.3、发生右单旋的具体场景

    看完上面的抽象旋转场景,肯定还有很多人云里雾里,接下来我们拆开每一颗抽象的子树内部的具体情况来分析。

    场景一:a/b/c三颗子树的高度都为0
    在这里插入图片描述

    这是最简单的一种情况,a、b、c 都只是空节点。当在 a 子树中插入新节点后,a所在是子树的高度从 0 变为 1,不断向上调整平衡因子,导致 10 的平衡因子从 -1 变成 -2,树失去平衡。
    此时对 10 进行一次右旋,5 上升为新的子树的根节点,5的右树 b 挂到 10 的左子树位置,树重新恢复平衡。
    场景二:a/b/c三颗子树的高度都为1
    当三棵子树高度为 1 时,内部结构会稍微复杂一些,但整体逻辑完全一致。如果在 a 子树中继续插入节点,a 的高度增加,最终使 10 的平衡因子变为 -2。
    此时同样通过一次右旋,把5的节点给10的的左边, 5 提升为子树根节点,恢复 AVL 树的平衡。在这里插入图片描述

    场景三:a/b/c三颗子树的高度都为2
    从现在开始,场景急剧复杂化。思考一个问题:当a/b/c子树高度为2时,子树的形状有多少种可能?给出答案如下图:一共有三种,我们标记为x,y,z。在这里插入图片描述
    b和c子树可以是xyz 三种情况中的任意一种,但是a就不一样了,先说结论:a子树的情况只有可能是x。怎么证明? 先看我们要做的事是在a子树插入一个结点。
    以下的情况bc是什么情况都可以因为我们要触发的右旋bc是什么在插入都不影响的我们就选择z了
    细分情况一: 如果a子树是y情况,那么就有3个黄色的位置可以插入但是这3个位置都不行,我们要的是让10这个节点变成-2触发右旋,在最下面两个插入的时候,a子树就不平衡了,于是直接以a子树的根为旋转结点进行右旋转,旋转后不再继续向上更新。即:在到达10之前提前结束
    在这里插入图片描述

    细分情况二: 如果a子树是z情况,那么在左子树插入后,a子树高度不变,如下图,当平衡因子更新到a子树的根节点,a子树根节点平衡因子从1->0,于是以a为根的子树高度不变,不再继续向上更新。最下面两个黄节点在a树会发生旋转,即:同样在到达10之前提前结束(循环结束)
    在这里插入图片描述

    所以,a子树必须是x情况。(在a子树插入结点的情况有4种,b和c子树各有3种情况,总计4x3x3=36种。在这里插入图片描述
    场景四~无穷:a/b/c三颗子树的高度都为3乃至更高
    当子树高度继续增大时,场景开始逐渐超出枚举范畴。还是思考一个问题:当a/b/c子树高度为3时,子树的形状有多少种可能?

    在这里插入图片描述

    如图,x:为高度为3的满二叉树的AVL树。y-C:代表一个组合,下面四个叶子节点保留任意1个/任意两个/任意3个,都满足高度3的AVL树。合计:C(4,1)+C(4,2)+C(4,3) = 4+6+4 = 14种形状。

    b和c可以是x/y-C中任意一种,组合:15*15,a的情况跟场景三类似,要满a必须插入新结点后,a自身不旋转,a高度+1不段向上更新,引发10结点旋转。

    a如果是x,插入位置可以是4个叶子的任意孩子位置,有8个。
    a如果是y-C中4个叶子节点保留3个有4种形状,插入位置在有两个结点那边任意孩子位置,有4个。

    组合一下:这里合计1515(8+4) = 5400种场景,由此可见,当子树的高度达到3的时候,情况种数就达到了如此高的水平(这就是前文为什么采用抽象图来解释原理的原因)。

    总结结论

    可以看到,无论 a / b / c 子树的高度是多少,只要满足抽象模型中的结构关系,发生失衡时都可以通过一次右旋恢复平衡。因此,前面的抽象模型实际上已经涵盖了所有可能的具体情况。

    4.3、右单旋代码

    让左子树的右子树链接到根的左子树、让根链接到左子树的右子树、让左子树成为根、将根节点和左子树的根节点的平衡因子置0即可

    直接看代码,我们在注释中详细介绍每一步的意义。,为了方便大家理解,我把抽象图再搬过来一份:在这里插入图片描述

    //右单旋
    void RotateR(Node* parent)
    {
    //左子树
    Node* subL = parent->_left;
    //左子树的右子树
    Node* subLR = subL->_right;
    //父节点
    Node* pparent = parent->_parent;

    //parent>subL>subLR

    //让左子树的右子树链接到根的左子树上 subLR<parent的
    parent->_left = subLR;
    //让根链接到左子树的右子树上, subL<parent的
    subL->_right = parent;//5结点变成新的根
    //父节点指向调整
    parent->_parent = subL;
    subL->_parent = pparent;
    //b可能是空节点
    if (subLR)
    subLR->_parent = parent;

    //如果10就是根节点要特殊处理一下
    if (_root == parent)
    {
    //根得到重定义
    _root = subL;
    subL->_parent = nullptr;
    }
    else
    {
    //处理 pparent 指向 subL
    if (pparent->_right == parent)
    pparent->_right = subL;
    else
    pparent->_left = subL;
    }
    //更新平衡因子
    subL->_bf = parent->_bf = 0;
    }

    4.4、左单旋代码

    左单旋的过程很简单 – 让右子树的左子树链接到根的左子树、让根链接到右子树的左子树、让右子树成为根、将根节点和右子树的根节点的平衡因子置0即可:
    在这里插入图片描述
    左单旋的原理和右单旋完全一致,只是方向完全相反。原理不再解释,以下我们直接给出代码:

    //左单旋
    void RotateL(Node* parent)
    {
    //右子树
    Node* subR = parent->_right;
    //右子树的左子树
    Node* subRL = subR->_left;
    //父节点
    Node* pparent = parent->_parent;

    //把subRL给parent的右子树
    parent->_right = subRL;

    //左子树当根
    subR->_left = parent;

    //维护共有的父节点们
    //b不是空树的情况下
    if (subRL)
    subRL->_parent = parent;
    parent->_parent = subR;
    subR->_parent = pparent;
    //特殊情况
    if (_root == parent)
    {
    _root = subR;
    subR->_parent = nullptr;//这个可以不写我们上面有涵盖
    }
    else
    {
    //说明不是根就要维护好pparent了
    if (pparent->_left == parent)
    pparent->_left = subR;
    else
    pparent->_right = subR;
    }

    //更新平衡因子
    subR->_bf = parent->_bf = 0;
    }

    4.5、左右双旋原理

    4.5.1、从具体案例引入

    我们上面讨论在左单旋和右单旋都是插入后为直线的情况,那么如果插入后是折线呢?如下:
    我们在a子树插入结点的时候,进行右单旋解决了AVL树失衡的问题。此时我们将目光转移到左子树,如果在左子树插入,如下图:
    b子树高度从h变成h+1,引发旋转,右单旋无法解决问题,右单旋后,我们的树依旧不平衡。
       在这里插入图片描述

    为什么呢?右单旋解决的纯粹的左边高,但是插入在b子树中,10为跟的子树不再是单纯的左边高,对于10是左边高,但是对于5是右边高,于是需要用两次旋转才能解决。以5为旋转点进行一个左单旋,以10为旋转点进行一个右单旋,这棵树就平衡了,如下图,我们以上面的后者情况为例:在这里插入图片描述

    4.5.2、发生左右双旋的抽象场景

    下面我们还是将a/b/c子树抽象为高度h的AVL子树进行分析,另外我们需要把b子树的细节进一步展开为b子树根和高度为h-1的e和f子树,因为我们要对b的父亲5为旋转点进行左单旋,左单旋需要动b树中的左子树。
    b子树中新增结点的位置不同,平衡因子更新的细节也不同,通过观察b子树根的平衡因子不同,这里我们要分三个场景讨论。
    ——————b子树根的平衡因子是旋转后平衡因子调整的根本
    在这里插入图片描述
    情况一
    h >= 1时,新增结点插入在e子树,e子树高度从h-1变为h并不断更新8->5->10平衡因子,引发旋转,其中8的平衡因子为-1,旋转后8和5平衡因子为0,10平衡因子为1。
    在这里插入图片描述

    情况二
    h >= 1时,新增结点插入在f子树,f子树高度从h-1变为h并不断更新8->5->10平衡因子,引发旋转,其中8的平衡因子为1,旋转后8和10平衡因子为0,5平衡因子为-1。
    在这里插入图片描述
    情况三
    h == 0时,a/b/c都是空树,b自己就是一个新增结点,不断更新5->10平衡因子,引发旋转,其中8的平衡因子为0,旋转后8和10和5平衡因子均为0。在这里插入图片描述

    我们可以在其中总结出一个结论:左右双旋的本质是将SubLR变成新的根,让SubL和parent分别变成SubLR的右左子树,让SubLR的左右子树分别变成SubL和parent的右子树和左子树。

    4.6、左右双旋代码

    //左右双旋
    void RotateLR(Node* parent)
    {
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    //保存SubLR的平衡因子,后面维护平衡因子可以根据结论+bf直接得出
    int bf=subLR->_bf;
    //进行左右双旋
    RotateL(subL);
    RotateR(parent);

    //平衡因子调节
    //情况一
    if (bf == 1)
    {
    subL->_bf = 0;
    subLR->_bf = 0;
    parent->_bf = 1;//SubLR子树中高为h的那一颗给了SubL,h-1的那一颗给了parent
    }
    //情况二
    else if (bf == 1)
    {
    subL->_bf = 1;//SubLR子树中高为h的那一颗给了parent,h-1的那一颗给了SubL
    subLR->_bf = 0;
    parent->_bf = 0;
    }
    //情况三
    else
    {
    subL->_bf = subLR->_bf = parent->_bf = 0;
    }

    }

    4.7、右左双旋代码

    右左双旋的代码和左右双旋的代码逻辑几乎一致,只是方向完全相反,以下直接给出代码和情况图解。在这里插入图片描述

    //右左双旋
    void RotateRL(Node* parent)
    {
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    int bf = subRL->_bf;

    RotateR(subR);
    RotateL(parent);

    if (bf == 1)
    {
    subRL->_bf = 0;
    parent->_bf = 0;
    subR->_bf = 1;
    }
    else if(bf==1)
    {
    subR->_bf = 0;
    subRL->_bf = 0;
    parent->_bf = 1;
    }
    else
    {
    subRL->_bf = 0;
    parent->_bf = 0;
    subR->_bf = 0;
    }
    }

    五、AVL树的部分简单的接口

    5.1、Find 查找函数

    //查找
    Node* Find(const K& key)
    {
    Node* cur = _root;
    while(cur)
    {
    if (key > cur->_kv.first)
    cur = cur->_right;
    else if (key < cur->_kv.first)
    cur = cur->_left;
    else
    return cur;
    }
    return nullptr;
    }

    5.2、size && height &&中序遍历

    这里要注意我们外面传进来的树我们要的是节点所以用子函数

    //size外面传进来的树我们要的是节点所以用子函数
    int size()
    {
    return _size(_root);
    }
    //高度
    int height()
    {
    return _height(_root);
    }
    int _size(Node* root)
    {
    if (root == nullptr)
    return 0;
    return 1 + _size(root->_left) + _size(root->_right);
    }
    int _height(Node* root)
    {
    if (root == nullptr)
    return 0;
    int LeftHeight = _height(root->_left);
    int RightHeight =_height(root->_right);

    return LeftHeight > RightHeight ? LeftHeight + 1: RightHeight + 1;
    }
    //中序遍历
    void inorder()
    {
    _inorder(_root);
    cout << endl;
    }
    void _inorder(Node* root)
    {
    if (root == nullptr)
    return;
    _inorder(root->_left);
    cout << root->_kv.first << "->" << root->_kv.second<<endl;
    _inorder(root->_right);
    }

    如何去检查我这棵树是不是AVL树,很多人第一时间会想到:直接检查平衡因子?看似可行,但是在工业中是严格不允许的。

    六、AVL树的判定

    有没有可能我这个平衡因子可能是对的,但是因为一种出乎意料的错误导致AVL树是错的.
    有没有一种可能我的树是对的,但是在更新平衡因子的时候因为一些意想不到的错误出了问题。
    所以我们必须对AVL树的每一个结点挨个儿去检查左右子树真实高度差才行。

    // 对外接口:用于判断整棵树是否为AVL树
    // 从根节点开始递归检查
    bool IsBalanceTree()
    {
    return _IsBalanceTree(_root);
    }

    // 递归检查AVL树是否合法
    bool _IsBalanceTree(Node* root)
    {
    // 递归终止条件:
    // 如果当前节点为空,说明已经走到叶子节点之后
    // 空树天然满足AVL树性质
    if (root == nullptr)
    return true;

    // 重新计算当前节点左右子树的真实高度
    // 注意:这里不相信节点中维护的bf,而是通过height函数重新计算
    int leftheight = _height(root->_left);
    int rightheight = _height(root->_right);

    // 根据高度计算当前节点的真实平衡因子
    // 平衡因子 = 右子树高度 – 左子树高度
    int Bf = rightheight leftheight;

    // 第一层检查:AVL树的基本性质
    // 任意节点左右子树高度差不能超过1
    if (abs(Bf) >= 2)
    {
    // 如果高度差>=2,说明树结构已经失衡
    cout << root->_kv.first << " height error" << endl;
    return false;
    }

    // 第二层检查:节点中存储的平衡因子是否正确
    // 在AVL树实现中,节点通常会维护一个_bf成员
    // 如果旋转或更新过程中出现错误,就可能导致_bf与真实值不一致
    if (root->_bf != Bf)
    {
    // 说明平衡因子维护出现问题
    cout << root->_kv.first<< "balance factor error" << endl;
    return false;
    }

    // 递归检查左右子树
    // 只有当前节点、左子树、右子树全部满足AVL条件,整棵树才是AVL树
    return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
    }

    七、总结&&测试

    AVL树的删除代码这里没有写,原因有二:
    ①面试很少涉及到这种难度
    ②难度太大了。
    有兴趣的大佬可以去看《殷人昆 数据结构:用面向对象方法与C++语言描述》,其中有详细讲解。(我后面回去研究研究回来补的)

    #pragma once

    #include <iostream>

    using namespace std;
    namespace ljm
    {
    //节点类(三叉链)
    template<class K, class V>
    struct AVLTreeNode
    {
    //构造节点
    AVLTreeNode(const pair<K, V>& kv)
    :_kv(kv)
    , _left(nullptr)
    , _right(nullptr)
    , _parent(nullptr)
    , _bf(0)
    {
    }

    pair<K, V> _kv;
    AVLTreeNode* _left;
    AVLTreeNode* _right;
    AVLTreeNode* _parent;//父节点
    int _bf;//平衡因子
    };

    //AVL树类
    template<class K, class V>
    class AVLTree
    {
    typedef struct AVLTreeNode<K, V> Node;
    public:
    AVLTree()
    :_root(nullptr)
    {
    }

    //右单旋
    void RotateR(Node* parent)
    {
    //左子树
    Node* subL = parent->_left;
    //左子树的右子树
    Node* subLR = subL->_right;
    //父节点
    Node* pparent = parent->_parent;

    //parent>subL>subLR

    //让左子树的右子树链接到根的左子树上 subLR<parent的
    parent->_left = subLR;
    //让根链接到左子树的右子树上, subL<parent的
    subL->_right = parent;//5结点变成新的根

    //父节点指向调整
    parent->_parent = subL;
    subL->_parent = pparent;
    //b可能是空节点
    if (subLR)
    subLR->_parent = parent;

    //如果10就是根节点要特殊处理一下
    if (_root == parent)
    {
    ////根得到重定义
    _root = subL;
    subL->_parent = nullptr;
    }
    else
    {
    //处理 pparent 指向 subL
    if (pparent->_right == parent)
    pparent->_right = subL;
    else
    pparent->_left = subL;
    }

    //更新平衡因子
    subL->_bf = parent->_bf = 0;
    }

    //左单旋
    void RotateL(Node* parent)
    {
    //右子树
    Node* subR = parent->_right;
    //右子树的左子树
    Node* subRL = subR->_left;
    //父节点
    Node* pparent = parent->_parent;

    //把subRL给parent的右子树
    parent->_right = subRL;

    //左子树当根
    subR->_left = parent;

    //维护共有的父节点们
    //b不是空树的情况下
    if (subRL)
    subRL->_parent = parent;
    parent->_parent = subR;
    subR->_parent = pparent;
    //特殊情况
    if (_root == parent)
    {
    _root = subR;
    subR->_parent = nullptr;//这个可以不写我们上面有涵盖
    }
    else
    {
    //说明不是根就要维护好pparent了
    if (pparent->_left == parent)
    pparent->_left = subR;
    else
    pparent->_right = subR;
    }

    //更新平衡因子
    subR->_bf = parent->_bf = 0;
    }

    //左右双旋
    void RotateLR(Node* parent)
    {
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    //保存SubLR的平衡因子,后面维护平衡因子可以根据结论+bf直接得出
    int bf = subLR->_bf;
    //进行左右双旋
    RotateL(subL);
    RotateR(parent);

    //平衡因子调节
    //情况一
    if (bf == 1)
    {
    subL->_bf = 0;
    subLR->_bf = 0;
    parent->_bf = 1;//SubLR子树中高为h的那一颗给了SubL,h-1的那一颗给了parent
    }
    //情况二
    else if (bf == 1)
    {
    subL->_bf = 1;//SubLR子树中高为h的那一颗给了parent,h-1的那一颗给了SubL
    subLR->_bf = 0;
    parent->_bf = 0;
    }
    //情况三
    else
    {
    subLR->_bf = 0;
    subL->_bf = 0;
    parent->_bf = 0;
    }
    }

    //右左双旋
    void RotateRL(Node* parent)
    {
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    int bf = subRL->_bf;

    RotateR(subR);
    RotateL(parent);

    if (bf == 1)
    {
    subRL->_bf = 0;
    parent->_bf = 0;
    subR->_bf = 1;
    }
    else if (bf == 1)
    {
    subR->_bf = 0;
    subRL->_bf = 0;
    parent->_bf = 1;
    }
    else
    {
    subRL->_bf = 0;
    parent->_bf = 0;
    subR->_bf = 0;
    }
    }

    //插入
    bool Insert(const K& key, const V& value)
    {
    //没有节点
    if (_root == nullptr)
    {
    _root = new Node(make_pair(key, value));
    return true;
    }

    Node* parent = nullptr;
    Node* cur = _root;
    Node* newnode = new Node(make_pair(key, value));
    while (cur)
    {
    if (key > cur->_kv.first)
    {
    parent = cur;
    cur = cur->_right;
    }
    else if (key < cur->_kv.first)
    {
    parent = cur;
    cur = cur->_left;
    }
    //相等不处理
    else
    {
    return false;
    }
    }
    //到这里就要到应该插入的位置了
    if (key > parent->_kv.first)
    {
    parent->_right = newnode;
    newnode->_parent = parent;
    }
    else
    {
    parent->_left = newnode;
    newnode->_parent = parent;
    }

    //cur更好表示
    cur = newnode;
    //更新平衡因子代码
    //因为只会影响父亲开始的祖父节点所以就先从父亲开始了
    //只要他变成0了就不会影响再上去的节点的
    while (parent)
    {
    //左边插入——
    if (parent->_left == cur)
    parent->_bf;
    //右边插入++
    else
    parent->_bf++;

    //只要他变成0了就不会影响再上去的节点的更新结束
    if (parent->_bf == 0)
    break;
    //bf为-1||1 说明是从0变的还要继续往上更新直到变成0或者根节点的父节点为空无法进入循环
    if (abs(parent->_bf) == 1)
    {
    cur = parent;
    parent = parent->_parent;
    }
    //bf为-2||2 说明是从1,-1变的这个时候不平衡了
    else if (abs(parent->_bf) == 2)
    {
    //旋转处理
    //右单旋
    if (parent->_bf == 2 && cur->_bf == 1)
    {
    RotateR(parent);
    }
    //右左双旋
    else if (parent->_bf == 2 && cur->_bf == 1)
    {
    RotateRL(parent);
    }
    // 左单旋
    else if (parent->_bf == 2 && cur->_bf == 1)
    {
    RotateL(parent);
    }
    //左右双旋
    else if (parent->_bf == 2 && cur->_bf == 1)
    {
    RotateLR(parent);
    }
    break;
    }
    }
    return true;
    }

    //查找
    Node* Find(const K& key)
    {
    Node* cur = _root;
    while (cur)
    {
    if (key > cur->_kv.first)
    cur = cur->_right;
    else if (key < cur->_kv.first)
    cur = cur->_left;
    else
    return cur;
    }
    return nullptr;
    }

    //size外面传进来的树我们要的是节点所以用子函数
    int size()
    {
    return _size(_root);
    }

    //高度
    int height()
    {
    return _height(_root);
    }

    //中序遍历
    void inorder()
    {
    _inorder(_root);
    cout << endl;
    }

    // 对外接口:用于判断整棵树是否为AVL树
    // 从根节点开始递归检查
    bool IsBalanceTree()
    {
    return _IsBalanceTree(_root);
    }

    private:
    Node* _root;

    int _size(Node* root)
    {
    if (root == nullptr)
    return 0;
    return 1 + _size(root->_left) + _size(root->_right);
    }

    int _height(Node* root)
    {
    if (root == nullptr)
    return 0;
    int LeftHeight = _height(root->_left);
    int RightHeight = _height(root->_right);

    return LeftHeight > RightHeight ? LeftHeight + 1 : RightHeight + 1;
    }

    void _inorder(Node* root)
    {
    if (root == nullptr)
    return;
    _inorder(root->_left);
    cout << root->_kv.first << "->" << root->_kv.second<<endl;
    _inorder(root->_right);
    }

    // 递归检查AVL树是否合法
    bool _IsBalanceTree(Node* root)
    {
    // 递归终止条件:
    // 如果当前节点为空,说明已经走到叶子节点之后
    // 空树天然满足AVL树性质
    if (root == nullptr)
    return true;

    // 重新计算当前节点左右子树的真实高度
    // 注意:这里不相信节点中维护的bf,而是通过height函数重新计算
    int leftheight = _height(root->_left);
    int rightheight = _height(root->_right);

    // 根据高度计算当前节点的真实平衡因子
    // 平衡因子 = 右子树高度 – 左子树高度
    int Bf = rightheight leftheight;

    // 第一层检查:AVL树的基本性质
    // 任意节点左右子树高度差不能超过1
    if (abs(Bf) >= 2)
    {
    // 如果高度差>=2,说明树结构已经失衡
    cout << root->_kv.first << " height error" << endl;
    return false;
    }

    // 第二层检查:节点中存储的平衡因子是否正确
    // 在AVL树实现中,节点通常会维护一个_bf成员
    // 如果旋转或更新过程中出现错误,就可能导致_bf与真实值不一致
    if (root->_bf != Bf)
    {
    // 说明平衡因子维护出现问题
    cout << root->_kv.first << "balance factor error" << endl;
    return false;
    }

    // 递归检查左右子树
    // 只有当前节点、左子树、右子树全部满足AVL条件,整棵树才是AVL树
    return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
    }
    };

    void test1()
    {
    AVLTree<int, string> tree;
    tree.Insert(1, "ljm");
    tree.Insert(5, "ljm");
    tree.Insert(4, "ljm");
    tree.Insert(3, "ljm");
    tree.Insert(0, "ljm");
    tree.Insert(52, "ljm");
    tree.Insert(32, "ljm");
    tree.Insert(42, "ljm");
    tree.inorder();
    cout << "高度" << tree.height() << endl;
    cout << "个数" << tree.size() << endl;
    cout << "是不是平衡二叉树:" << tree.IsBalanceTree() << endl;
    }
    }

    在这里插入图片描述

    赞(0)
    未经允许不得转载:171主机测评 » 【C++】 STL篇 AVL树的模拟实现
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址