平衡二叉树(AVL树)

简介

平衡二叉树,又称为AVL树,为了优化排序二叉树极端情况(从小到大或从大到小插入,呈现链状)而衍生出来的结构

特点

1.基于排序二叉树

2.任意节点的左右子树的高度差都小于2

因此,同排序二叉树一样,平衡二叉树只需要在插入和删除时,进行平衡,使得左右子树高度差均小于1即可

查询

所有二叉排序树的查询过程都是一致的,包括平衡二叉树和红黑树,都是通过二分法向下查询,可以参考二叉排序树的查询

节点基本数据结构

typedef struct TreeNode {
    int data;
    struct TreeNode *parentNode;
    struct TreeNode *l, *r;
}LSTreeNode;
复制代码

旋转

旋转是当增删处理完成之后,无法自平衡的最终解决手段,平衡二叉树和红黑树都会用到

因此旋转是了解插入和删除之前,必须要知道的知识点,可参考上一章二叉树的旋转

左旋代码

void swapLeftRotation(LSTreeNode *parentNode, LSTreeNode *rightNode) {
    int data = parentNode->data;
    parentNode->data = rightNode->data;
    rightNode->data = data;
    parentNode->r = rightNode->r;
    rightNode->r->parentNode = parentNode;
    rightNode->r = rightNode->l;
    rightNode->l = parentNode->l;
    if (parentNode->l) parentNode->l->parentNode = rightNode;
    parentNode->l = rightNode;
}
复制代码

右旋代码

void swapRightRotation(LSTreeNode *parentNode, LSTreeNode *leftNode) {
    int data = parentNode->data;
    parentNode->data = leftNode->data;
    leftNode->data = data;
    parentNode->l = leftNode->l;
    leftNode->l->parentNode = parentNode;
    leftNode->l = leftNode->r;
    leftNode->r = parentNode->r;
    if (parentNode->r) parentNode->r->parentNode = leftNode;
    parentNode->r = leftNode;
}
复制代码

失衡类型与平衡手段

平衡二叉树插入和删除之前还必须要知道的知识点:失衡类型和平衡手段

平衡二叉树一旦处于失衡状态,会出现四种平衡类型:LL型、LR型、RR型、RL型

其中LL型、LR型分别于RR型、RL型为对称关系

LL型

即:指定节点的左子节点的左子节点分支,比指定节点的右分支高度要高,从而导致的失衡状态

如下图所示,则为LL型失衡状态常见的几种状态

如上图所示,他们的失衡对照点均在A节点处,左右子树高度差达到2,其中子节点为空的(A的右子节点),高度则为0

注意:如果B、F、G节点同时不存在,那么结果是一样的只不过是携带空节点的旋转而已

处理方案:直接右旋(即:对AC进行右旋)

依次的变换结果如下图所示

LR型

即:指定节点的左子节点的右子节点分支,比指定节点的右分支高度要高,从而导致的失衡状态

如下图所示,则为LR型失衡状态常见的几种状态

如上图所示,他们的失衡对照点均在A节点处,左右子树高度差达到2,(发现直接对AC右旋无法平衡该状态)

注意:如果B、D、G节点同时不存在,那么结果是一样的只不过是携带空节点的旋转而已,这里就不做介绍了

处理方案:先左旋后右旋(即:先对CF进行左旋,在对AF进行右旋)

依次的变换结果如下图所示

RR型

即:指定节点的有子节点的有子节点分支,比指定节点的左分支高度要高,从而导致的失衡状态

如下图所示,则为RR型失衡状态常见的几种状态

如上图所示,他们的失衡对照点均在A节点处,左右子树高度差达到2,其中子节点为空的(A的左子节点),高度则为0

注意:如果B、F、G节点同时不存在,那么结果是一样的只不过是携带空节点的旋转而已

处理方案:直接左旋(即:对AC进行左旋)

依次的变换结果如下图所示

RL型

即:指定节点的右子节点的左子节点分支,比指定节点的左分支高度要高,从而导致的失衡状态

如下图所示,则为RL型失衡状态常见的几种状态

如上图所示,他们的失衡对照点均在A节点处,左右子树高度差达到2,(发现直接对AC左旋无法平衡该状态)

注意:如果B、D、G节点同时不存在,那么结果是一样的只不过是携带空节点的旋转而已,这里就不做介绍了

处理方案:先右旋后左旋(即:先对CF进行右旋,在对AF进行左旋)

依次的变换结果如下图所示

获取高度

平衡二叉树通过检测左右分支高度差来判别是否失衡,因此需要获取节点的高度(深度)

int getHeight(LSTreeNode *node) {
    int heightX = 0;
    while (node) {
        heightX++;
        node = node->l ? node->l : node->r;
    }
    return heightX;
}
复制代码

插入

平衡二叉树插入的过程,依赖排序二叉树的插入,即:插入后维持平衡

正常插入一个元素后,参考失衡的四种状态,对二叉树进行检测和平衡

常见的插入后失衡结构(以插入左侧黑色节点I为例,局部相似记得忽略,避免陷入过多情形陷阱)如下:

平衡步骤

1.正常往排序二叉树插入一个元素,并标记好父节点

2.从插入节点开始,查看祖父点是否存在(父节点一定存在,否则为根节点插入),如果不存在结束(到达根节点),

3.对比祖父节点左右分支是否高度差达到失衡条件,该祖父节点这里成为称为失衡对照点

4.如果祖父节点不存在失衡状态,则父节点变成插入节点,祖父节点变成父节点,继续自下向上查找(保证本次插入节点与其他分支节点不失衡,直到根节点),重复步骤2; 否则进行下一步

5.通过比较的祖父节点的左右分差结果,判断哪边高则是插入边

6.插入点在失衡点左侧:则查看是LL型,还是LR型,此时通过插入点的父节点,查看右孩子是否是插入节点,如果是则为LR型、否则为LL型(可以参考上面的图);

7.如果为LL型,直接对插入节点的父节点和插入节点的祖父节点进行右旋即可;如果为LR型,先对插入节点的父节点和插入节点进行左旋,然后在对父节点和祖父节点右旋即可

8.插入点在失衡点右侧:则查看是RR型,还是RL型,此时通过插入点的父节点,查看左孩子是否是插入节点,如果是则为RL型、否则为RR型(可以参考上面的图);

9.如果为RR型,直接对插入节点的父节点和插入节点的祖父节点进行左旋即可;如果为RL型,先对插入节点的父节点和插入节点进行右旋旋,然后在对点和祖父节点左旋即可

代码实现

#pragma mark --平衡二叉树的插入(再二叉排序树的基础上进行改进)
LSTreeNode *insertData(LSTreeNode *root, int data) {
    LSTreeNode *node = (LSTreeNode *)malloc(sizeof(LSTreeNode));
    node->data = data;
    if (!root) return node;
    LSTreeNode *p = root, *s = NULL;//s为插入节点的位置
    while (p) {
        s = p;
        if (p->data == data) {
            free(node);
            return root; //重复可以进行替换值结束
        }
        if (p->data > data) {
            p = p->l;
        }else {
            p = p->r;
        }
    }
    _Bool isLeft = s->data > data;
    if (isLeft) {
        s->l = node;
    }else {
        s->r = node;
    }
    node->parentNode = s; //设置父节点
    //正常的插入一个新节点到此结束
    //此时需要开始平衡二叉树
    insertBalanceNode(root, s, node);
    return root;
}

//平衡插入的新节点, 以插入节点父节点为基准
LSTreeNode *insertBalanceNode(LSTreeNode *root, LSTreeNode *parentNode, LSTreeNode *insertNode) {
    LSTreeNode *grandNode = parentNode->parentNode;
    //不存在则到达根节点,不满足两层,不需要平衡
    if (!grandNode) return root;
    //计算左右分支差
    int heightX = getHeight(grandNode->l) - getHeight(grandNode->r);
    //分支高度差小于2才需要直接平衡,否则一直平衡到树根
    if (abs(heightX) < 2) {
        //需要自下而上平衡到根节点,只平衡一个树是不行的,可能当前树内部本身就是平衡的,但是和另外一个数出现了高度差
        //父节点代替当前节点继续向上平衡,毕竟要一层一层向上走
        return insertBalanceNode(root, grandNode, parentNode);
    }
    //此时需要直接平衡平衡
    //注意细节:因为是插入节点,node一定是指向插入的分支方向的那个根节点的子节点
    if (heightX > 0) {
        //插入分支属于左分支,查看左分支是插入到了node左边还是右边
        //处理不平衡
        if (parentNode->r == insertNode) {
            //如果是从右侧插入,先左旋
            swapLeftRotation(parentNode, insertNode);
        }
        //直接整体右旋即可平衡
        swapRightRotation(grandNode, parentNode);
    }else {
        //右侧分支较多
        if (parentNode->l == insertNode) {
            //如果是从左侧插入,先左旋
            swapRightRotation(parentNode, insertNode);
        }
        swapLeftRotation(grandNode, parentNode);
    }
    //平衡完一次后消除当前插入造成的结果,直接结束即可
    return root;
}
复制代码

删除

平衡二叉树插删除的过程,依赖排序二叉树的删除,即:插入后维持平衡

正常删除一个元素后,需要处理好其父子节点的交接(尤其是相比排序二叉树新增加的父节点的交接),再参考失衡的四种状态,对二叉树进行检测和平衡

常见的删除结构(以删除右侧黑色节点X为例,局部相似记得忽略,避免陷入过多情形陷阱)如下:

平衡步骤

1.正常往排序二叉树中删除该元素,并正确维护好父子节点交接

2.查看删除节点的父节点是否存在,如果不存在则删除的纯根节点,结束,否则进行下一步

3.保存删除节点的兄弟节点和父节点,开始平衡排序二叉树

4.如果父节点不存在,则已经平衡或者对比到根节点了,则不需要平衡直接结束即可

5.对比父节点左右分支是否高度差达到失衡条件,该父节点这里成为称为失衡对照点

6.如果父节点不存在失衡状态,则父节点变成兄弟节点,祖父节点变成父节点,继续自下向上查找(保证本次删除与其他分支节点不失衡,直到根节点),重复步骤2; 否则进行下一步

7.删除节点的兄弟节点在失衡点左侧:则查看是LL型,还是LR型,由于无法直接确定失衡的类型,因此需要通过兄弟节点的左右分支高度差,来确定失衡类型(可对比插入),如果左边高度小于右侧,则为LR型,否则默认为LL型(一样高度的情况怎么处理都行,以LL最简单)

7.如果为LL型,直接对插入节点的父节点和插入节点的祖父节点进行右旋即可;如果为LR型,先对插入节点的父节点和插入节点进行左旋,然后在对父节点和祖父节点右旋即可

8.删除节点的兄弟节点在失衡点右侧:则查看是RR型,还是RL型,由于无法直接确定失衡的类型,因此需要通过兄弟节点的左右分支高度差,来确定失衡类型(可对比插入),如果左边高度大于右侧,则为RL型,否则默认为RR型(一样高度的情况怎么处理都行,以LL最简单)

9.如果为RR型,直接对插入节点的父节点和插入节点的祖父节点进行左旋即可;如果为RL型,先对插入节点的父节点和插入节点进行右旋旋,然后在对点和祖父节点左旋即可

#pragma --mark --平衡二叉树的删除(在删除的基础上进行平衡)
LSTreeNode *deleteData(LSTreeNode *root, int data) {
    if (!root) return NULL;
    LSTreeNode *p = root, *s = NULL;
    while (p) {
        if (p->data == data) break;
        s = p;
        if (p->data < data) {
            p = p->r;
        }else {
            p = p->l;
        }
    }
    if (!p) return root; //没找到删除失败
    
    if (!p->l && !p->r) {
        //为叶子节点
        if (p == root) root = NULL;
        else if (s->l == p) s->l = NULL;
        else s->r = NULL;
    }else if (!p->l && p->r) {
        //只有右节点
        if (p == root) root = s->r;
        else if (s->l == p) s->l = p->r;
        else s->r = p->r;
        if (p->r) p->r->parentNode = s;
    }else if (p->l && !p->r) {
        //只有左节点
        if (p == root) root = s->l;
        else if (s->l == p) s->l = p->l;
        else s->r = p->l;
        if (p->l) p->l->parentNode = s;
    }else {
        //找到左侧最后一个节点替换掉删除节点内容,然后删除左节点最后一个内容
        LSTreeNode *q = p; //记录被删除的及诶单
        s = p; //保存删除点的父节点
        p = p->l; //找到要替换的删除节点的节点
        while (p->r) {
            q = p;
            p = p->r;
        }
        q->data = p->data;
        if (s == q) {
            s->l = p->l;
            if (p->l) p->l->parentNode = s;
        }
        else {
            q->r = p->l;
            if (p->l) p->l->parentNode = q;
        }
    }
    //父节点存在才有平衡必要(不存在说明删除的为根节点)
    if (s) {
        LSTreeNode *brotherNode = s->l == p ? s->r : s->l;
        
        root = deleteBalanceNode(root, s, brotherNode);
    }
    free(p);
    return root;
}

//平衡二叉树的删除(在删除的基础上进行平衡),从删除节点向上平衡,那么与插入相似,当前分支作为缺少的分支,找另外一个分支对比平衡,如果平衡,那么当前分支取父节点继续向上递归平衡即可
LSTreeNode *deleteBalanceNode(LSTreeNode *root, LSTreeNode *parentNode, LSTreeNode *brotherNode) {
    //不存在则到达根节点,不满足两层,不需要平衡
    if (!parentNode) return root;
    
    int heightX = getHeight(parentNode->l) - getHeight(parentNode->r);
    if (abs(heightX) < 2) {
        //平衡这的直接向上对比一直倒根节点方可确认没有失衡
        //注意:可以通过父节点和删除节点自动排除同时存在两个节点情况(同时左右孩子删除完不影响平衡),其实起始处判断一次,可以优化效率
        return deleteBalanceNode(root, parentNode->parentNode, parentNode);
    }
    //出现了不平衡开始平衡,部分同插入一致
    if (heightX > 0) {
        //右侧为删除节点所在树枝,左侧偏高,从左侧平衡
        if (getHeight(brotherNode->l) - getHeight(brotherNode->r) < 0) {
            //此时为LR型,先局部左旋,此时右侧节点一定存在
            swapLeftRotation(brotherNode, brotherNode->r);
        }
        //直接整体右旋即可平衡
        swapRightRotation(parentNode, brotherNode);
    }else {
        //左侧为删除节点所在树枝,右侧偏高,从右侧平衡
        if (getHeight(brotherNode->l) - getHeight(brotherNode->r) > 0) {
            //此时为RL型,先局部右旋,此时左侧节点一定存在
            swapRightRotation(brotherNode, brotherNode->l);
        }
        //直接整体左旋即可平衡
        swapLeftRotation(parentNode, brotherNode);
    }
    //平衡完毕后即可直接结束,每一次删除最多只会出现一次失衡
    return root;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值