算法训练营 查找算法(平衡二叉树)

平衡二叉树

  • 二叉查找树的查找、插入、删除的时间复杂度均线性正比于二叉查找树的高度,高度越小,效率越高。
  • 最好的情况下,每次都一分为二,左右子树的节点数均为n/2,左右子树的高度也一样。

原理 AVL树

  • 平衡二叉查找树,简称平衡二叉树(AVL)
  • 平衡二叉树或为空树,或为具有以下性质的平衡二叉树:①左右子树高度差的绝对值不超过1;②左右子树也是平衡二叉树
  • 平衡二叉树除了具有适度平衡性,还具有局部性:①在单次插入、删除后,至多有O(1)处出现不平衡;②总可以在O(logn)时间内,使这O(1)处不平衡重新调整为平衡。
  • 对平衡二叉树在动态修改后出现的不平衡,只需局部(最小不平衡子树)调整平衡即可,不需要对整颗树进行调整。

调整平衡的方法

  • 调整平衡可以分为4种情况: LL型、RR型、LR型、RL型。
LL型
  • 插入新节点x后,从该节点向上找到最近的不平衡节点A,如果最近不平衡节点到新节点的路径前两个都是左子树L,就是LL型。
  • 即将节点x插入A的左子树的左子树中,A的左子树因插入新节点而高度增加,造成A的平衡因子由1增加为2,失去平衡。需要进行LL旋转(顺时针)调整平衡。
  • LL旋转:A顺时针旋转到B的右子树,B原来的右子树T_{3}被抛弃,A旋转后正好左子树空闲,将这个被抛弃的子树 T 3 T_{3} T3放到A的左子树中即可。
  • 旋转之后,A、B两个节点的左右子树高度之差均为0,满足平衡条件,C的左右子树未变,仍然平衡。
AVLTree LL_Rotation(AVLTree &T)//LL旋转
{
    AVLTree temp=T->lchild;
    T->lchild=temp->rchild;
    temp->rchild=T;
    updateHeight(T);//更新高度
    updateHeight(temp);
    return temp;
}

RR型
  • 插入新节点x后,从该节点向上找到最近不平衡节点A,如果最近不平衡节点到新节点的路径前两个都是右子树R,就是RR型。需要进行RR旋转(逆时针)调整平衡。
  • RR旋转:A逆时针旋转到B的左子树,B原来的左子树T_{2}被抛弃,A旋转后正好右子树空闲,将这个被抛弃的子树 T 2 T_{2} T2放到A右子树中即可。
  • 旋转之后,A、B两个节点的左右子树高度之差均为0,满足平衡条件,C的左右子树未变,仍然平衡。
AVLTree RR_Rotation(AVLTree &T)//RR旋转
{
    AVLTree temp=T->rchild;
    T->rchild=temp->lchild;
    temp->lchild=T;
    updateHeight(T);//更新高度
    updateHeight(temp);
    return temp;
}
LR型
  • 插入新节点x后,从该节点向上找到最近不平衡节点A,如果最近不平衡节点到新节点的路径前两个依次是左子树L、右子树R,就是LR型。
  • LR旋转:分两次旋转。C逆时针旋转到A、B之间,C原来的左子树 T 2 T_{2} T2被抛弃,B正好右子树空闲,将这个被抛弃的子树 T 2 T_{2} T2放到B右子树中;这时已经转变为LL型,进行LL旋转即可,实际上也可以看作C固定不动,B进行RR旋转,然后进行LL旋转。
  • 旋转之后,A、B两个节点的左右子树高度之差均为0,满足平衡条件,C的左右子树未变,仍然平衡。
AVLTree LR_Rotation(AVLTree &T)//LR旋转
{
     T->lchild=RR_Rotation(T->lchild);
     return LL_Rotation(T);
}
RL型
  • 插入新节点x后,从该节点向上找到最近不平衡节点A,如果最近不平衡节点到新节点的路径前两个依次是右子树R、左子树L,就是RL型。
  • RL旋转:分两次旋转。C顺时针旋转到A、B之间,C原来的右子树 T 3 T_{3} T3被抛弃,B正好左子树空闲,这个被抛弃的子树 T 3 T_{3} T3放到B左子树;这时已经转变为RR型,做RR旋转即可,实际上,也可以看作C固定不动,B进行LL旋转,然后进行RR旋转。
  • 旋转之后,A、B两个节点的左右子树高度之差均为0,满足平衡条件,C的左右子树未变,仍然平衡。
AVLTree RL_Rotation(AVLTree &T)//RL旋转
{
    T->rchild=LL_Rotation(T->rchild);
    return RR_Rotation(T);
}

平衡二叉树的插入

  • 在平衡二叉树中插入新的数据元素 x x x,首先要查找其插入的位置,在查找过程中,用 p p p指针记录当前节点,用 f f f指针记录 p p p的双亲。
  1. 在平衡二叉树中查找 x x x,如果查找成功,则什么也不做,返回 p p p;如果查找失败,则执行插入操作。
  2. 创建一个新节点 p p p存储 x x x,该节点的双亲为 f f f,高度为1。
  3. 从新节点子父 f f f出发,向上查找最近的不平衡节点。逐层检查各代祖先节点,如果平衡,则更新其高度,继续向上查找;如果不平衡,则判断失衡类型(沿着高度最大的子树判断,刚插入新节点的子树必然高度大),并作出相应的调整,返回p。
AVLTree Insert(AVLTree &T,int x)
{
    if(T==NULL) //如果为空,创建新结点
    {
        T=new AVLNode;
        T->lchild=T->rchild=NULL;
        T->data=x;
        T->height=1;
        return T;
     }
    if(T->data==x) return T;//查找成功,什么也不做,查找失败时才插入
    if(x<T->data)//插入到左子树
    {
        T->lchild=Insert(T->lchild,x);//注意插入后饭后结果挂接到T->lchild
        if(Height(T->lchild)-Height(T->rchild)==2)//插入后看是否平衡,如果不平衡显然是插入的那一边高度大
        {                                         //沿着高度大的那条路径判断
            if(x<T->lchild->data)//判断是LL还是LR,即插入的是lchild节点的lchild 还是rchild
                T=LL_Rotation(T);
            else
                T=LR_Rotation(T);
        }
    }
    else//插入到右子树
    {
        T->rchild=Insert(T->rchild,x);
        if(Height(T->rchild)-Height(T->lchild)==2)
        {
            if(x>T->rchild->data)
                T=RR_Rotation(T);
            else
                T=RL_Rotation(T);
        }
    }
    updateHeight(T);
    return T;
}

平衡二叉树的创建

  • 平衡二叉树的创建和二叉查找树的创建类似,只是插入操作多了调整平衡而已。可以从空树开始,按照输入关键字的顺序依次进行插入操作,最终得到一颗平衡二叉树。
  1. 初始化平衡二叉树为空树,T = NULL。
  2. 输入一个关键字x,将x插入平衡二叉树T中。
  3. 重复步骤2,直到关键字输入完毕。
AVLTree CreateAVL(AVLTree &T)
{
    int n,x;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>x;
        T=Insert(T,x);
    }
    return T;
}

平衡二叉树的删除

  • 进行删除操作时需要一直从删除节点之父向上检查,发现不平衡便立即调整,然后继续向上检查,直到树根。
  1. 在平衡二叉树中查找x,如果查找失败,则返回;如果查找成功,则执行删除操作(同二叉查找树的删除操作)。
  2. 从实际被删除节点之父g出发(当被删除节点有左右子树时,令其直接前驱(或直接后继)代替其位置,删除其之间前驱,实际被删除节点为其之间前驱(或直接后继)),向上寻找最近的不平衡节点。逐层检查各代祖先节点,如果平衡,则更新其高度,继续向上寻找;如果不平衡,则判断失衡类型(沿着高度大的子树判断),并做相应的调整。
  3. 继续向上检查,一直到树根。
AVLTree adjust(AVLTree &T)//删除结点后,需要判断是否还是平衡,如果不平衡,就要调整
{
    if(T==NULL) return NULL;
    if(Height(T->lchild)-Height(T->rchild)==2)//沿着高度大的那条路径判断
    {
        if(Height(T->lchild->lchild)>=Height(T->lchild->rchild))
            T=LL_Rotation(T);
        else
            T=LR_Rotation(T);
    }
    if(Height(T->rchild)-Height(T->lchild)==2)//沿着高度大的那条路径判断
    {
        if(Height(T->rchild->rchild)>=Height(T->rchild->lchild))
            T=RR_Rotation(T);
        else
            T=RL_Rotation(T);
    }
    updateHeight(T);
    return T;
}

AVLTree Delete(AVLTree &T,int x)
{
    if(T==NULL) return NULL;
    if(T->data==x)//如果找到删除节点
    {
        if(T->rchild==NULL)//如果该节点的右孩子为NULL,那么直接删除
        {
            AVLTree temp=T;
            T=T->lchild;
            delete temp;
        }
        else//否则,将其右子树的最左孩子作为这个节点,并且递归删除这个节点的值
        {
           AVLTree temp;
           temp=T->rchild;
           while(temp->lchild)
              temp=temp->lchild;
           T->data=temp->data;
           T->rchild=Delete(T->rchild,T->data);
           updateHeight(T);
        }
        return T;
    }

    if(T->data>x)//调节删除节点后可能涉及的节点
        T->lchild=Delete(T->lchild,x);
    if(T->data<x)
        T->rchild=Delete(T->rchild,x);
    updateHeight(T);
	T=adjust(T);
    return T;
}

整体算法实现

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

typedef struct AVLNode{
   int data;
   int height;
   struct AVLNode *lchild;
   struct AVLNode *rchild;
}*AVLTree;

AVLTree Empty(AVLTree &T)//删除树
{
    if(T==NULL) return NULL;
    Empty(T->lchild);
    Empty(T->rchild);
    delete T;
    return NULL;
}

inline int Height(AVLTree T)//计算高度
{
    if(T==NULL) return 0;
    return T->height;
}

void updateHeight(AVLTree &T)
{
     T->height=max(Height(T->lchild),Height(T->rchild))+1;
}

AVLTree LL_Rotation(AVLTree &T)//LL旋转
{
    AVLTree temp=T->lchild;
    T->lchild=temp->rchild;
    temp->rchild=T;
    updateHeight(T);//更新高度
    updateHeight(temp);
    return temp;
}

AVLTree RR_Rotation(AVLTree &T)//RR旋转
{
    AVLTree temp=T->rchild;
    T->rchild=temp->lchild;
    temp->lchild=T;
    updateHeight(T);//更新高度
    updateHeight(temp);
    return temp;
}

AVLTree LR_Rotation(AVLTree &T)//LR旋转
{
     T->lchild=RR_Rotation(T->lchild);
     return LL_Rotation(T);
}

AVLTree RL_Rotation(AVLTree &T)//RL旋转
{
    T->rchild=LL_Rotation(T->rchild);
    return RR_Rotation(T);
}

AVLTree Insert(AVLTree &T,int x)
{
    if(T==NULL) //如果为空,创建新结点
    {
        T=new AVLNode;
        T->lchild=T->rchild=NULL;
        T->data=x;
        T->height=1;
        return T;
     }
    if(T->data==x) return T;//查找成功,什么也不做,查找失败时才插入
    if(x<T->data)//插入到左子树
    {
        T->lchild=Insert(T->lchild,x);//注意插入后饭后结果挂接到T->lchild
        if(Height(T->lchild)-Height(T->rchild)==2)//插入后看是否平衡,如果不平衡显然是插入的那一边高度大
        {                                         //沿着高度大的那条路径判断
            if(x<T->lchild->data)//判断是LL还是LR,即插入的是lchild节点的lchild 还是rchild
                T=LL_Rotation(T);
            else
                T=LR_Rotation(T);
        }
    }
    else//插入到右子树
    {
        T->rchild=Insert(T->rchild,x);
        if(Height(T->rchild)-Height(T->lchild)==2)
        {
            if(x>T->rchild->data)
                T=RR_Rotation(T);
            else
                T=RL_Rotation(T);
        }
    }
    updateHeight(T);
    return T;
}

AVLTree adjust(AVLTree &T)//删除结点后,需要判断是否还是平衡,如果不平衡,就要调整
{
    if(T==NULL) return NULL;
    if(Height(T->lchild)-Height(T->rchild)==2)//沿着高度大的那条路径判断
    {
        if(Height(T->lchild->lchild)>=Height(T->lchild->rchild))
            T=LL_Rotation(T);
        else
            T=LR_Rotation(T);
    }
    if(Height(T->rchild)-Height(T->lchild)==2)//沿着高度大的那条路径判断
    {
        if(Height(T->rchild->rchild)>=Height(T->rchild->lchild))
            T=RR_Rotation(T);
        else
            T=RL_Rotation(T);
    }
    updateHeight(T);
    return T;
}

AVLTree Delete(AVLTree &T,int x)
{
    if(T==NULL) return NULL;
    if(T->data==x)//如果找到删除节点
    {
        if(T->rchild==NULL)//如果该节点的右孩子为NULL,那么直接删除
        {
            AVLTree temp=T;
            T=T->lchild;
            delete temp;
        }
        else//否则,将其右子树的最左孩子作为这个节点,并且递归删除这个节点的值
        {
           AVLTree temp;
           temp=T->rchild;
           while(temp->lchild)
              temp=temp->lchild;
           T->data=temp->data;
           T->rchild=Delete(T->rchild,T->data);
           updateHeight(T);
        }
        return T;
    }

    if(T->data>x)//调节删除节点后可能涉及的节点
        T->lchild=Delete(T->lchild,x);
    if(T->data<x)
        T->rchild=Delete(T->rchild,x);
    updateHeight(T);
	T=adjust(T);
    return T;
}

void Preorder(AVLTree T)//前序遍历方便看树的结果
{
    if(T==NULL) return ;
    cout<<T->data<<"\t"<<T->height<<endl;
    Preorder(T->lchild);
    Preorder(T->rchild);
}

 void Inorder(AVLTree T)//中序遍历方便看树的结果
{
    if(T==NULL) return ;
    Inorder(T->lchild);
    cout<<T->data<<"\t"<<T->height<<endl;
    Inorder(T->rchild);
}

 void Posorder(AVLTree T)//后序遍历方便看树的结果
{
    if(T==NULL) return ;
    Posorder(T->lchild);
    Posorder(T->rchild);
    cout<<T->data<<"\t"<<T->height<<endl;
}

void show(AVLTree T)
{
    Preorder(T);
    cout<<endl;
    Inorder(T);
    cout<<endl;
    Posorder(T);
}

AVLTree CreateAVL(AVLTree &T)
{
    int n,x;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>x;
        T=Insert(T,x);
    }
    return T;
}
int main()
{
    int x;
    AVLTree root=NULL;
    root=Empty(root);
    CreateAVL(root);
    show(root);
    cin>>x;
    root=Delete(root,x);
    show(root);
    return 0;
}

输入:

6
25 18 5 10 15 17
5

输出:

15      3
10      2
5       1
18      2
17      1
25      1

5       1
10      2
15      3
17      1
18      2
25      1

5       1
10      2
17      1
25      1
18      2
15      3

15      3
10      1
18      2
17      1
25      1

10      1
15      3
17      1
18      2
25      1

10      1
17      1
25      1
18      2
15      3
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

羽星_s

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值