[日记]LeetCode算法·二十五——二叉树⑤ AVL树(插入+删除)附代码实现

news2024/11/27 20:34:37

本章的代码实现基于上一篇BST与优先队列的基类进行平衡二叉树,即AVL树。

文章目录

  • AVL的概念
  • AVL查询效率
  • AVL的插入
    • 1.插入节点
    • 2.更新平衡因子BF
    • 3.旋转调整树的结构
      • 3.1 LL 右旋
      • 3.2 RR 左旋
      • 3.3 LR 左右双旋
      • 3.4 RL 右左双旋
    • 4 插入总结
  • AVL的删除
    • 1.寻找删除节点
    • 2.更新平衡因子BF + 旋转
    • 3.实际删除节点
    • 4.删除总结
  • 总结

AVL的概念

总所周知,BST在插入数据随机的情况下,其搜索能达到O(logn)的性能,但如果插入数据有序,或是经过若干次的插入与删除,BST将会退化,甚至变为线性的链表,这不利于搜索。
如何保持BST的优秀查找性质,同时又不至于过分的维护成本(例如完全二叉树),AVL树就是其中一个答案。
AVL树通过维护左右子树高度差,从而保证了搜索的效率,AVL的定义如下:

  1. AVL树要么是空树,那么满足以下两个条件
  2. AVL树的左右子树也是AVL树
  3. AVL节点的平衡因子绝对值不超过1,平衡因子(balance factor)定义:左右子树高度差=左子树高度-右子树高度

AVL查询效率

AVL树的查询效率同样为O(logn),具体证明如下:
使用数学归纳法,假设高度为h的AVL树,其所能容纳的最少节点数为N(h)(即容纳N个节点时,AVL最大即最糟糕的高度),可以发现满足以下情况:

  1. h=1时,N(h)=1
  2. h=2时,N(h)=2
  3. 当h>=3时,最糟糕的树必然根节点BF不为0(因为BF=0时,左右子树都高h-1,容纳节点必然多与1个h-2子树+1个h-1子树),那么此时最糟糕的树= 1个根节点 + 1个h-1的子树 + 1个h-2的子树,即N(h)= 1 + N(h-1) + N(h-2)
  4. G(h)=N(h)+1,则G(h)=F(h+2),F为斐波那契数列,而随着i的增大,斐波那契数列有一个性质: F i F i − 1 → 5 + 1 2 = Φ \frac{F_i}{F_{i-1}}→\frac{\sqrt{5}+1}{2}=\Phi Fi1Fi25 +1=Φ
  5. 可以估算i较大时, F i ≈ Φ i 5 F_i\approx\frac{\Phi^i}{\sqrt{5}} Fi5 Φi N h = F h + 2 − 1 = Φ h + 2 5 − 1 N_h=F_{h+2}-1=\frac{\Phi^{h+2}}{\sqrt{5}}-1 Nh=Fh+21=5 Φh+21 h = l o g N h + 1 − 2 l o g Φ + 1 2 l o g 5 l o g Φ ≈ 1.44 l o g N h + C h=\frac{logN_h+1-2log\Phi+\frac{1}{2}log5}{log\Phi}\approx1.44logN_h+C h=logΦlogNh+12logΦ+21log51.44logNh+C
  6. 故得证AVL的搜索效率为O(logn),在最糟糕的情况下,其搜索效率仅为完全二叉树的1.44倍,退化性能不多。

AVL的插入

AVL的查找与BST完全一致,因此无需赘述,较为困难的是AVL的插入与删除,因为必须维护AVL的平衡因子,因此涉及BF的更新与树的旋转
再进一步之前,我们需要意识到以下几点:

  1. AVL是递归定义的,AVL的左右子树都是AVL树
  2. 上一条性质意味着,如果一个节点失衡,只会影响局部而不一定是整体。那么通过调整局部的子树,可以达到整体的平衡
  3. 树的旋转前后,如果不改变树的高度,那么子树平衡的同时不影响父节点的BF,说明调整完毕,否则需要继续递归向上调整

1.插入节点

插入节点与BST一致,唯一的区别在于,AVL树我们使用了三叉链bF,需要注意parent节点的连接与bF的默认置零

void insert(int key)
{
    TreeNode* cur = this->root;
    TreeNode* pre = cur;
    TreeNode* node = new TreeNode(key);
    //利用pre和cur找到插入的位置
    while (cur)
    {
        pre = cur;
        if (key < cur->val)
            cur = cur->left;
        else
            cur = cur->right;
    }
    //根节点
    if (this->root == nullptr)
    {
        this->root = node;
        return;
    }
    //在左边
    if (key < pre->val)
        pre->left = node;
    //右边
    else
        pre->right = node;
    node->parent = pre;
    cur = node;
    //.......
}

2.更新平衡因子BF

当我们插入一个新的节点时,必然会影响父节点的BF值,如果改变了父节点的高度,则会影响组父节点的BF,因此我们必须向上溯源更新BF值,更新原则如下:

  • 若插入的值key < 溯源节点pre,说明新节点位于pre的左子树,左子树高度增大,pre->bF++
  • 若插入的值key > 溯源节点pre,说明新节点位于pre的右子树,右子树高度增大,pre->bF–

现在我们考虑更新后的pre的平衡因子bF,从而判断是否继续向上溯源,分析如下:

  1. 首先明确,根据AVL的定义更新前pre的bF可能取值为-1,0,1
  2. 若更新后bF为0,说明更新前为-1或1,且新节点插入了较低的子树,插入较低子树意味着pre的高度不变,无需继续溯源,插入完成,跳出循环
  3. 若更新后bF为-1或1,说明更新前为0,两子树高度一致,在插入新节点后,其中一颗子树高度增大,因此pre的高度发生变化,需要继续溯源,pre=pre->parent,直到根节点为止。
  4. 若更新后bF为-2或2,说明更新前为-1或1,且新节点插入了较高的子树,此时,pre失衡,且pre为失衡的最小子树,需要进行旋转调整,因为insert造成的失衡可以通过1次旋转完成调整,并且使pre的BF=0,因此跳出循环,进入旋转模块
void insert(int key)
{
    //......
    //上接插入节点
    bool unbanlance = false;
    //更新bF值
    while (pre)
    {
        //沿着搜索路径向上回溯,修改bF
        if (key < pre->val)
            ++pre->bF;
        else
            --pre->bF;
        //平衡
        if (pre->bF == 0)
            return;
        //pre处失衡
        else if (pre->bF == 2 || pre->bF == -2)
        {
            unbanlance = true;
            break;
        }
        //继续向上调整
        else
        {
            cur = pre;
            pre = pre->parent;
        }
    }
    //......
}

3.旋转调整树的结构

调整树的结构,我们可以对失衡情况进行分类,共有以下4类。

3.1 LL 右旋

如图所示:
在这里插入图片描述
LL代表着这样一种情况:

  • 插入前,P节点的BF为1,L节点的BF为0(必然是这种情况,不可能P为1且L为1,否则P不是最先找到节点),代表着P的左子树比后子树高1,L的左右子树一致
  • 插入后,L和P的左子树高度增加1,P节点的BF为2,L节点的BF为1。此时P节点失衡,我们采用右旋,下降P节点,上升L节点。
  • 右转后,L和P节点的BF值都归0
//a为失衡节点,b为a的左节点
void LL(TreeNode* a, TreeNode* b)
{
    a->left = b->right;
    if (b->right != nullptr)
        b->right->parent = a;
    b->right = a;
    b->parent = a->parent;
    a->parent = b;
    a->bF = 0;
    b->bF = 0;
    if (b->parent==nullptr)
        this->root = b;
    else if (b->val < b->parent->val)
        b->parent->left = b;
    else
        b->parent->right = b;
}

3.2 RR 左旋

如图所示:
在这里插入图片描述
RR对应着LL的对称情况,不必多说。

//a为失衡节点,b为a的右节点
void RR(TreeNode* a, TreeNode* b)
{
    bool isRoot = a->parent == nullptr;
    a->right = b->left;
    if (b->left != nullptr)
        b->left->parent = a;
    b->left = a;
    b->parent = a->parent;
    a->parent = b;
    a->bF = 0;
    b->bF = 0;
    if (b->parent == nullptr)
        this->root = b;
    else if (b->val < b->parent->val)
        b->parent->left = b;
    else
        b->parent->right = b;
}

3.3 LR 左右双旋

如图所示:
在这里插入图片描述
LR对应着这一种情况:

  • 插入前,与LL的情况一致。
  • 插入后,L的右子树高度增加1,而P的左子树高度增加1,P节点的BF为2,L节点的BF为-1,所需要进行的调整较为复杂,但可以拆分为两步进行。
  • 首先对L、LR进行一次左旋,下降L,上升LR。之后对P、LR进行一次右旋,下降P,上升LR。
  • 左右双旋后,LR的BF=0,而L和P的BF则需要根据插入节点所位于LR的位置进行判断(也可根据LR之前的BF进行判断),如果插入在LR的左子树,则L->BF=0,P->BF=-1插在LR的右子树,则L->BF=1,P->BF=0
//a为失衡节点,b为a的左节点
void LR(TreeNode* a, TreeNode* b)
{
    TreeNode* c = b->right;
    b->right = c->left;
    a->left = c->right;
    if (c->left != nullptr)
        c->left->parent = b;
    if (c->right != nullptr)
        c->right->parent = a;
    c->left = b;
    c->right = a;
    c->parent = a->parent;
    b->parent = c;
    a->parent = c;
    //c就是插入节点
    if (c->bF == 0)
    {
        a->bF = 0;
        b->bF = 0;
    }
    //插入节点在c的左子树
    else if (c->bF = 1)
    {
        b->bF = 0;
        a->bF = -1;
    }
    else
    {
        b->bF = 1;
        a->bF = 0;
    }
    c->bF = 0;
    if (c->parent == nullptr)
        this->root = c;
    else if (c->val < c->parent->val)
        c->parent->left = c;
    else
        c->parent->right = c;
}

3.4 RL 右左双旋

如图所示:
在这里插入图片描述
RL对应着LR的对称情况,不必多说。

//a为失衡节点,b为a的右节点
void RL(TreeNode* a, TreeNode* b)
{
    TreeNode* c = b->left;
    b->left = c->right;
    a->right = c->left;
    if (c->right != nullptr)
        c->right->parent = b;
    if (c->left != nullptr)
        c->left->parent = a;

    c->left = a;
    c->right = b;
    c->parent = a->parent;
    a->parent = c;
    b->parent = c;

    if (c->bF == 0)
    {
        a->bF = 0;
        b->bF = 0;
    }
    else if (c->bF = 1)
    {
        a->bF = 0;
        b->bF = -1;
    }
    else
    {
        a->bF = 1;
        b->bF = 0;
    }
    c->bF = 0;
    if (c->parent == nullptr)
        this->root = c;
    else if (c->val < c->parent->val)
        c->parent->left = c;
    else
        c->parent->right = c;
}

4 插入总结

最后我们对插入做一个总结,具体过程如下:

  1. 首先,从根节点出发,找到新插入节点的位置(空节点)和其父节点
  2. 插入节点
  3. 从插入节点的父节点开始,向上回溯更新BF
  4. 若是更新后的BF=1或-1,则继续更新,直到根节点为止;若是更新后的BF=0,则插入结束,返回;若是更新后的BF=2或-2,则找到了最小的失衡AVL子树,跳出循环,修复该子树。
  5. 若是失衡,则根据失衡节点a和插入节点所在分支的子节点b的BF值,判断是LL/RR/LR/RL中哪种情况,并进行相应的旋转操作。

完整代码如下:

//插入
void insert(int key)
{
    TreeNode* cur = this->root;
    TreeNode* pre = cur;
    TreeNode* node = new TreeNode(key);
    //利用pre和cur找到插入的位置
    while (cur)
    {
        pre = cur;
        if (key < cur->val)
            cur = cur->left;
        else
            cur = cur->right;
    }
    //根节点
    if (this->root == nullptr)
    {
        this->root = node;
        return;
    }
    //在左边
    if (key < pre->val)
        pre->left = node;
    //右边
    else
        pre->right = node;
    node->parent = pre;
    cur = node;
    bool unbanlance = false;
    //更新bF值
    while (pre)
    {
        //沿着搜索路径向上回溯,修改bF
        if (key < pre->val)
            ++pre->bF;
        else
            --pre->bF;
        //平衡
        if (pre->bF == 0)
            return;
        //pre处失衡
        else if (pre->bF == 2 || pre->bF == -2)
        {
            unbanlance = true;
            break;
        }
        //继续向上调整
        else
        {
            cur = pre;
            pre = pre->parent;
        }
    }
    //失衡状态需要调整
    if (unbanlance)
    {
        //LL型
        if (pre->bF == 2 && cur->bF == 1)
            LL(pre, cur);
        else if (pre->bF == 2 && cur->bF == -1)
            LR(pre, cur);
        else if (pre->bF == -2 && cur->bF == -1)
            RR(pre, cur);
        else
            RL(pre, cur);
    }
    return;
}

AVL的删除

相比于插入,AVL的删除实际上可能更加困难,正如BST的删除也比插入更难。
与BST一致,我们依然是从叶节点、单边节点和双边节点开始考虑。

1.寻找删除节点

我们删除节点的流程应为:找到并记录删除节点->更新BF值->调整树结构->实际删除节点。

  • 无删除节点,返回
  • 叶节点,记录下该节点和父节点
  • 单边节点,记录下该节点和父节点
  • 双边节点,采用替换删除法,采用前驱(左子树最大值)或后继(右子树最小值)替换该删除节点的值,实际删除节点为前驱或后继节点,本程序采用后继,记录下后继节点和父节点。
  • 删除节点是叶节点或单边节点,同时是根节点的情况,需要特殊处理。(双边节点实际上删的是后继节点,所以不需要单独处理)
void remove(int key)
{
    TreeNode* cur = this->root;
    TreeNode* pre = nullptr;
    TreeNode* deleteNode = nullptr;
    TreeNode* deleteParent = nullptr;
    while (cur)
    {
        if (key > cur->val)
        {
            pre = cur;
            cur = cur->right;
        }
        else if (key < cur->val)
        {
            pre = cur;
            cur = cur->left;
        }
        //找到了需要删除的节点
        else
        {
            //需要删除节点的左子树为空
            if (cur->left == nullptr)
            {
                //若是根节点,将右子树作为新的根节点即可
                //根节点没有父节点,无需更新bF
                if (cur->parent == nullptr)
                {
                    root = cur->right;
                    if (root)
                        root->parent = nullptr;
                    delete(cur);
                    return;
                }
                else
                {
                    //记录信息
                    deleteNode = cur;
                    deleteParent = pre;
                }
            }
            else if (cur->right == nullptr)
            {
                if (cur->parent == nullptr)
                {
                    root = cur->left;
                    if (root)
                        root->parent = nullptr;
                    delete(cur);
                    return;
                }
                else
                {
                    deleteNode = cur;
                    deleteParent = pre;
                }
            }
            else
            {
                //左右子树都非空,进行替换,并且更新需要删除的位置
                //利用rightMin进行更新
                TreeNode* minRight = cur->right;
                while (minRight->left)
                    minRight = minRight->left;
                //替换
                cur->val = minRight->val;
                //标记删除节点
                deleteNode = minRight;
                deleteParent = minRight->parent;
            }
            break;
        }
    }

    //没有需要删除的节点
    if (deleteParent == nullptr)
        return;
    //.......
}

2.更新平衡因子BF + 旋转

不同于插入,在删除之中,旋转可能会改变树的高度,因此更新BF和旋转必须在一个循环中反复进行,不能拆分进行。

毫无疑问,我们依然需要确立更新原则,更新原则如下:

  • 若删除的节点 < 溯源节点deleteParent,说明删除节点位于deleteParent的左子树,左子树高度减小,deleteParent->bF–
  • 若删除的节点 > 溯源节点deleteParent,说明删除节点位于deleteParent的右子树,右子树高度增孝,deleteParent->bF++

现在我们考虑更新后的deleteParent的平衡因子bF,从而判断是否继续向上溯源,分析如下:

  1. 依然明确,根据AVL的定义更新前deleteParent的bF可能取值为-1,0,1
  2. 若更新后bF为0,说明更新前为-1或1,删除了较高子树的节点,继续向上回溯
  3. 若更新后bF为-1或1,说明更新前为0,两子树高度一致,删除其中一棵子树的节点,树的高度没有发生边海,至此插入结束。
  4. 若更新后bF为-2或2,说明更新前为-1或1,且删除了较低子树的节点,此时,deleteParent失衡,需要进行旋转调整,旋转调整分为6种情况,其中4种情况会改变树的高度,需要继续向上回溯

以下为失衡时的6种情况,以及对应的处理方法:

  1. 当deleteParent的平衡因子BF为2,deleteParent的左孩子平衡因子为1时,即降低了R节点的树高,与插入时的LL情况一致,采用右旋旋转前的子树路径为P->L->L的左子树,高度为1+1+h旋转后的子树路径为L->L的左子树,高度为1+h,右子树为L->P->(L右子树h-1)或(P右子树h-1),高度由h+2→h+1,必须继续回溯。
  2. 当deleteParent的平衡因子BF为2,deleteParent的左孩子平衡因子为-1时,即降低了R节点的树高,与插入时的LR情况一致,采用左右双旋,同上进行分析,高度h+2→h+1,继续回溯。
  3. 当deleteParent的平衡因子BF为2,deleteParent的左孩子平衡因子为0时,此时情况较为特殊,降低了R的树高,但是L的两个子树高度一致,这是插入中没有的情况,我们采用右旋+改变平衡因子调整方法的方法进行,右旋后,将L->BF=-1,P->BF=1,旋转前后的高度h+2→h+1,高度没有变化,不需要继续回溯
  4. 当deleteParent的平衡因子BF为-2,deleteParent的左孩子平衡因子为-1时,第1种情况的对称情况,左旋,继续回溯。
  5. 当deleteParent的平衡因子BF为-2,deleteParent的左孩子平衡因子为1时,第2种情况的对称情况,右左双旋,继续回溯
  6. 当deleteParent的平衡因子BF为-2,deleteParent的左孩子平衡因子为0时,第3种情况的对称情况,左旋+改变平衡因子调整方法,将R->BF=1,P->BF=-1不需要继续回溯
void remove(int key)
{
    //.....
    //上接寻找删除节点
    //备份
    TreeNode* delP = deleteParent;
    TreeNode* del = deleteNode;
    //更新bF
    while (deleteParent)
    {
        //删除左子树
        if (deleteNode->val < deleteParent->val)
            --deleteParent->bF;
        else
            ++deleteParent->bF;
        //根据bF进一步判断
        //bF=0,说明原来为-1 或者 1,此时改变了树的高度,需要继续向上更新
        if (deleteParent->bF == 0)
        {
            deleteNode = deleteParent;
            deleteParent = deleteParent->parent;
        }
        //bF=1 / -1,说明原来为0,此时没有修改树的高度(高度由最高的子树决定),不需要继续更新
        else if (deleteParent->bF == 1 || deleteParent->bF == -1)
        {
            break;
        }
        //bF=2 / -2,失衡,需要进行旋转
        else
        {
            //左边子树高
            if (deleteParent->bF == 2)
            {
                //LL情况
                if (deleteParent->left->bF == 1)
                    LL(deleteParent, deleteParent->left);
                //LR
                else if (deleteParent->left->bF == -1)
                    LR(deleteParent, deleteParent->left);
                else
                {
                    //由于右子树的降低而导致的失衡,左节点的两个子树高度一致
                    //可以采用LL进行处理,但需要重新调整bF
                    LL(deleteParent, deleteParent->left);
                    //调整deleteNode和左子节点如今的位置
                    deleteParent = deleteParent->parent;
                    //旋转前,左子树=1节点+2个节点的子树AB(高h) 高h+1;右子树=1个子树C(高h-1) 高h-1
                    //旋转后,左子树=子树A 高h,右子树=原来根节点 + 左子树B(高h)+右子树C(高h-1) 高h+1
                    //因此,新的根节点左子树低于右子树,右节点的子树则是左子树高于右子树
                    deleteParent->bF = -1;
                    deleteNode->right->bF = 1;
                    //此时,树的高度没有发生变化,不需要继续向上更新,故break
                    break;
                }
            }
            else
            {
                //RR
                if (deleteParent->right->bF == -1)
                    RR(deleteParent, deleteParent->right);
                //RL
                else if (deleteParent->right->bF == 1)
                    RL(deleteParent, deleteParent->right);
                else
                {
                    RR(deleteParent, deleteParent->right);
                    deleteParent = deleteParent->parent;
                    deleteParent->bF = 1;
                    deleteParent->left->bF = -1;
                    break;
                }
            }

            //旋转会调整树的高度,需要继续更新(不需要更新的情况已经break了)
            deleteNode = deleteParent;
            deleteParent = deleteParent->parent;
        }
    }
    //......
}

3.实际删除节点

利用备份好的删除节点信息,考虑单边节点和删除节点所位于的子树情况进行删除。

void remove(int key)
{
    //.....
    //上接bf调整和旋转
    //删除节点(必然有一颗子树为空)
    //删除节点的左子树为空
    if (del->left == nullptr)
    {
        //删除节点位于左子树
        if (del->val < delP->val)
            delP->left = del->right;
        else
            delP->right = del->right;
        if (del->right != nullptr)
            del->right->parent = delP;
    }
    //右子树为空
    else
    {
        if (del->val < delP->val)
            delP->left = del->left;
        else
            delP->right = del->left;
        //此时delteNode->left必然不为nullptr(这种情况已经讨论过)
        del->left->parent = delP;
    }
    delete(del);
    return;
}

4.删除总结

相比于插入,删除需要注意的情况更多,且存在旋转改变高度,上层父节点也需要旋转的可能。
在此就不再列删除流程,而是记录一些关键点:

  1. 对于删除的节点种类的选择:叶节点、单边节点、双边节点,双边采用替换删除法转为叶节点或单边节点
  2. 更新BF和旋转树需要同时在循环内进行,循环停止条件为不改变树高度或到达根节点
  3. 不改变树高度分为删除本身不改变旋转后恢复删除前高度两种情况,后者只在父节点的BF=2或-2,且子节点BF=0时出现

总体代码如下:

void remove(int key)
{
    TreeNode* cur = this->root;
    TreeNode* pre = nullptr;
    TreeNode* deleteNode = nullptr;
    TreeNode* deleteParent = nullptr;
    while (cur)
    {
        if (key > cur->val)
        {
            pre = cur;
            cur = cur->right;
        }
        else if (key < cur->val)
        {
            pre = cur;
            cur = cur->left;
        }
        //找到了需要删除的节点
        else
        {
            //需要删除节点的左子树为空
            if (cur->left == nullptr)
            {
                //若是根节点,将右子树作为新的根节点即可
                //根节点没有父节点,无需更新bF
                if (cur->parent == nullptr)
                {
                    root = cur->right;
                    if (root)
                        root->parent = nullptr;
                    delete(cur);
                    return;
                }
                else
                {
                    //记录信息
                    deleteNode = cur;
                    deleteParent = pre;
                }
            }
            else if (cur->right == nullptr)
            {
                if (cur->parent == nullptr)
                {
                    root = cur->left;
                    if (root)
                        root->parent = nullptr;
                    delete(cur);
                    return;
                }
                else
                {
                    deleteNode = cur;
                    deleteParent = pre;
                }
            }
            else
            {
                //左右子树都非空,进行替换,并且更新需要删除的位置
                //利用rightMin进行更新
                TreeNode* minRight = cur->right;
                while (minRight->left)
                    minRight = minRight->left;
                //替换
                cur->val = minRight->val;
                //标记删除节点
                deleteNode = minRight;
                deleteParent = minRight->parent;
            }
            break;
        }
    }

    //没有需要删除的节点
    if (deleteParent == nullptr)
        return;

    //备份
    TreeNode* delP = deleteParent;
    TreeNode* del = deleteNode;
    //更新bF
    while (deleteParent)
    {
        //删除左子树
        if (deleteNode->val < deleteParent->val)
            --deleteParent->bF;
        else
            ++deleteParent->bF;
        //根据bF进一步判断
        //bF=0,说明原来为-1 或者 1,此时改变了树的高度,需要继续向上更新
        if (deleteParent->bF == 0)
        {
            deleteNode = deleteParent;
            deleteParent = deleteParent->parent;
        }
        //bF=1 / -1,说明原来为0,此时没有修改树的高度(高度由最高的子树决定),不需要继续更新
        else if (deleteParent->bF == 1 || deleteParent->bF == -1)
        {
            break;
        }
        //bF=2 / -2,失衡,需要进行旋转
        else
        {
            //左边子树高
            if (deleteParent->bF == 2)
            {
                //LL情况
                if (deleteParent->left->bF == 1)
                    LL(deleteParent, deleteParent->left);
                //LR
                else if (deleteParent->left->bF == -1)
                    LR(deleteParent, deleteParent->left);
                else
                {
                    //由于右子树的降低而导致的失衡,左节点的两个子树高度一致
                    //可以采用LL进行处理,但需要重新调整bF
                    LL(deleteParent, deleteParent->left);
                    //调整deleteNode和左子节点如今的位置
                    deleteParent = deleteParent->parent;
                    //旋转前,左子树=1节点+2个节点的子树AB(高h) 高h+1;右子树=1个子树C(高h-1) 高h-1
                    //旋转后,左子树=子树A 高h,右子树=原来根节点 + 左子树B(高h)+右子树C(高h-1) 高h+1
                    //因此,新的根节点左子树低于右子树,右节点的子树则是左子树高于右子树
                    deleteParent->bF = -1;
                    deleteNode->right->bF = 1;
                    //此时,树的高度没有发生变化,不需要继续向上更新,故break
                    break;
                }
            }
            else
            {
                //RR
                if (deleteParent->right->bF == -1)
                    RR(deleteParent, deleteParent->right);
                //RL
                else if (deleteParent->right->bF == 1)
                    RL(deleteParent, deleteParent->right);
                else
                {
                    RR(deleteParent, deleteParent->right);
                    deleteParent = deleteParent->parent;
                    deleteParent->bF = 1;
                    deleteParent->left->bF = -1;
                    break;
                }
            }

            //旋转会调整树的高度,需要继续更新(不需要更新的情况已经break了)
            deleteNode = deleteParent;
            deleteParent = deleteParent->parent;
        }
    }

    //删除节点(必然有一颗子树为空)
    //删除节点的左子树为空
    if (del->left == nullptr)
    {
        //删除节点位于左子树
        if (del->val < delP->val)
            delP->left = del->right;
        else
            delP->right = del->right;
        if (del->right != nullptr)
            del->right->parent = delP;
    }
    //右子树为空
    else
    {
        if (del->val < delP->val)
            delP->left = del->left;
        else
            delP->right = del->left;
        //此时delteNode->left必然不为nullptr(这种情况已经讨论过)
        del->left->parent = delP;
    }
    delete(del);
    return;
}

总结

总算把AVL树的博客写完了,我发现大量的博客确实缺少了对于AVL删除的叙述,有些可惜。
之后的红黑树、B树、B+树、哈夫曼树,估计不会自己实现,而是记录一下思路和细节,也没有必要再费劲地去处理红黑树N多种情况。
——2023.5.17

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

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

相关文章

窗口组件元素识别工具

inspect.exe 微软官方工具集成于 Windows SDK 官网下载&#xff1a;https://developer.microsoft.com/zh-cn/windows/downloads/windows-sdk/ FlaUInspect&#xff1a; 第三方开源的识别工具 https://github.com/FlaUI/FlaUInspect UIRecorder&#xff1a; WinAppDriver里…

无状态子域名爆破工具(附下载)

ksubdomain是一款基于无状态子域名爆破工具&#xff0c;支持在Windows/Linux/Mac上使用&#xff0c;它会很快的进行DNS爆破&#xff0c;在Mac和Windows上理论最大发包速度在30w/s,linux上为160w/s的速度。 总的来说&#xff0c;ksubdomain能爆破/验证域名&#xff0c;并且快和…

【P25】JMeter 取样器超时(Sample Timeout)

文章目录 一、准备工作二、测试计划设计 一、准备工作 慕慕生鲜&#xff1a; http://111.231.103.117/#/login 进入网页后&#xff0c;登录&#xff0c;页面提供了账户和密码 搜索框输入“虾” 右键检查或按F12&#xff0c;打开调试工具&#xff0c;点击搜索 二、测试计划设…

CHATGPT:北京打响大模型地方战第一枪

5月16日消息&#xff0c;最近&#xff0c;“北京市促进通用人工智能创新发展的若干措施&#xff08;2023-2025年&#xff09;&#xff08;征求意见稿&#xff09;”&#xff08;以下简称“措施”&#xff09;公布。这个措施从算力、数据、算法、应用、监管五个方向出发&#xf…

Redis学习---03

一、redis事务 (1) Redis单条命令保证原子性&#xff0c;但事务不保证原子性。 Redis 事务不是严格意义上的事务&#xff0c;只是用于帮助用户在一个步骤中执行多个命令。单个 Redis 命令的执行是原子性的&#xff0c;但 Redis 没有在事务上增加任何维持原子性的机制&#xf…

STL好难(2):string类的使用

【本节目标】 1. 标准库中的string类2. string类的模拟实现3. 扩展阅读 目录 【本节目标】 1.标准库中的string类 2. string类对象的常见构造 &#x1f349;无参构造 &#x1f349;带参构造 &#x1f349;拷贝构造 &#x1f349;用n字符 # 去初始化 &#x1f349;用字…

二进制部署高可用Kubernetes集群

SUMMARY 架构图 设备规划 序号名字功能VMNET 1备注 1备注 2备注 3 备注 4备注 50orgin界面192.168.164.10haproxykeepalived192.168.164.2001reporsitory仓库192.168.164.16yum 仓库registoryhaproxykeepalived2master01H-K8S-1192.168.164.11kube-apicontrollerscheduler…

约瑟夫问题及求解方法

文章目录 什么是约瑟夫问题&#xff1f;求解方法代码实现 什么是约瑟夫问题&#xff1f; 约瑟夫问题是一个经典的数学难题&#xff0c;其一般形式可以描述为&#xff1a; n个人&#xff08;编号从1到n&#xff09;&#xff0c;围坐在一张圆桌周围。从第一个人开始报数&#x…

chatgpt赋能Python-mac系统的python

在Mac系统上运行Python&#xff1a;一个简介 介绍 Python是一种流行的、易于学习的编程语言&#xff0c;被广泛用于各种用途&#xff0c;从数据分析到机器学习。如果您是Mac用户&#xff0c;那么您已经安装了Python&#xff0c;因为它是系统的一部分。本文将介绍如何在Mac系统…

Web基础 ( 五 ) JavaScript BOM

4.4.BOM浏览器对象模型 window代表窗体, 内置多种对象, 每种对象包含多种方法及属性 4.4.1.location 地址栏 window.location.href "url"; // 当前窗口加载指定的页面location.reload(); //刷新4.4.2.history 访问历史记录 window.history.back(); // 返回上一…

搞一搞用例图

前言 基于公司技术方案的设计比较重视用例图&#xff0c;重新学习一下相关内容。用例要说明参与者与用例之间的关系&#xff0c;那么对用例图相关要点进行梳理 用例图的定义及组成要素用例图的4种关系常用的用例图软件 定义与组成 用例图核心作用是将系统需求和参与者之间的…

DEJA_VU3D - Cesium功能集 之 110-椭圆(标绘+编辑)

前言 编写这个专栏主要目的是对工作之中基于Cesium实现过的功能进行整合,有自己琢磨实现的,也有参考其他大神后整理实现的,初步算了算现在有差不多实现小140个左右的功能,后续也会不断的追加,所以暂时打算一周2-3更的样子来更新本专栏(每篇博文都会奉上完整demo的源代码…

【手撕红黑树】

前言 相信很多人初学者听到了红黑树后心中不免有些心慌&#xff0c;那你看到了这篇文章后相信会有所收获&#xff0c;我其实刚开始也是对红黑树抱着一种害怕甚至是恐惧&#xff0c;但是在老师的帮助下也终于慢慢的不在恐惧了&#xff0c;你想知道为什么的话就继续往下看吧。&am…

【C,C++】内存管理new和delete

内存管理 前言正式开始几道热身题C语言动态内存管理方式C内存管理new/delete操作内置类型new和delete对于内置类型new开辟失败 operator new与operator delete函数new和delete的实现原理内置类型自定义类型 定位new表达式面试常考&#xff1a;malloc/free和new/delete的区别 前…

板子短路了?

有段时间没更新了&#xff0c;主要是最近有点忙&#xff0c;当然也因为有点“懒”。 做这行业的都知道&#xff0c;下半年都是比较忙的&#xff0c;相信大家也是&#xff01; 相信做硬件的小伙伴们&#xff0c;遇到过短路的板子已经不计其数了。 短路带来的危害&#xff1a;…

关于单目视觉 SLAM 的空间感知定位技术的讨论

尝试关于单目视觉 SLAM 的空间感知定位技术的学习&#xff0c;做以调查。SLAM算法最早在机器人领域中提出&#xff0c;视觉SLAM又可以分为单目、双目和深度相机三种传感器模式&#xff0c;在AR应用中通常使用轻便、价格低廉的单目相机设备。仅使用一个摄像头作为传感器完成同步…

Web基础 ( 四 ) JavaScript 介绍

4.JavaScript 4.1.概念 4.1.1.什么是JavaScript 通过浏览器中内置的解析器&#xff0c;逐行解析执行的一种脚本语言 主要是处理系统使用者的行为逻辑的 4.1.2.与Java语言的比较 代码格式不同 ​ Java与HTML无关的格式 ​ JavaScript代码是一种文本字符格式&#xff0c;可…

chatgpt赋能Python-numpy归一化函数

介绍&#xff1a;numpy归一化函数 在数据处理和分析中&#xff0c;常常需要将数据归一化到一定范围内&#xff0c;以便于不同数据之间进行比较和处理。在Python的数据科学方面&#xff0c;numpy库是非常常用的工具之一&#xff0c;其中的归一化函数非常便捷和有效。 在这篇文…

如何快速入门 Java?

在一线互联网公司做开发 13 年了&#xff0c;“精通”Java&#xff0c;“吊打”一众面试官&#xff0c;如何快速入门 Java&#xff0c;对我来说简直就是小儿科&#xff0c;相信看完后你一定能收获满满、醍醐灌顶&#xff0c;今年秋招拿下阿里、美团等互联网大厂的 offer。 逼装…

django ORM框架 第二章 表与表的关系关联表

目录 一、表的几种关联关系 1.1 一对一 1、介绍&#xff1a; 2、举例 3、建表原则&#xff1a; 4、django ORM 框架实现 一对一 的表的创建 1.2 一对多 1、介绍&#xff1a; 2、举例 3、建表原则&#xff1a; 4、django ORM 框架实现 一对多 的表的创建 1.3 多对多 1…