平衡二叉树(AVL)(一)

本文深入探讨了平衡二叉搜索树的基本概念及其关键特性,包括如何维持树的平衡状态来确保高效的查找、删除和插入操作。文章还详细介绍了在插入新节点后,通过旋转操作来恢复树的平衡性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

平衡二叉搜索树是具有某些特殊性质的二叉搜索树,对于每一个节点 p , 它的左右子树的高度相差不超过 1。将二叉排序树维持在平衡状态。因此查找,删除,插入的操作的时间复杂度维持在 O(log2n) .
查找元素,遍历,找最大最小值,求树的高度,这些基本操作和普通的二叉搜索树几乎一样,直接借用二叉搜索树的成员函数,具体实现何以参见
二叉搜索树
对其进行插入元素时,首先按照普通的二叉搜索树插入,但是插入元素之后可能导致叶子节点的高度差大于1,这时候需要进行节点的旋转,恢复平衡二叉树的性质。
如下图插入键值为 7 节点,很显然新的排序二叉树的不再满足平衡二叉树的性质。
这里写图片描述

旋转

由于任意节点最多有两个儿子,高度不平衡时,此节点的两颗子树的高度相差2,有以下几种情况:
有以下几种情况

case1:左左情况 13 节点的左子树 8节点 的高度比右子树 15节点 的高度大 2, 左子树 8节点的 左子树 5节点 的高度大于 右子树 10节点

case2:左右情况 13 节点的左子树 8节点 的高度比右子树 15节点 的高度大 2, 左子树 8节点的 右子树 10节点 的高度大于 右子树 5节点

case3:右左情况 9 节点的右子树 15节点 的高度比左子树 8节点 的高度大 2, 右子树 15节点的 左子树 12节点 的高度大于 右子树 18节点

case3:右右情况 9 节点的右子树 15节点 的高度比左子树 8节点 的高度大 2, 右子树 15节点 的右子树 18节点 的高度大于 左子树 12节点

单旋转:
case1 和 case4 属于对称情况,只需要一次旋转就可以,下面分析case1 左左情况 singRotateLeft(k1) :(节点k1 不符合平衡二叉树的性质)

这里写图片描述
由对称性对于case4执行 singRotateRight(K1)。

void avlTree::singRotateLeft(Node *p) // case1:左左情况左旋
{
    Node *pLchild = p->lchild;
    this->transplant(p, pLchild);
    p->lchild = pLchild->rchild;
    if (pLchild->rchild) // 可能待左转节点的左孩子没有右孩子
    {
        pLchild->rchild->parent = p;
    }
    pLchild->rchild = p;
    p->parent = pLchild;
}
void avlTree::singRotateRight(Node *p) // case4:右右情况
{
    Node *pRchild = p->rchild;
    this->transplant(p, pRchild);
    p->rchild = pRchild->lchild;
    if (pRchild->lchild) // 可能待右转节点的右孩子没有左孩子
    {
        pRchild->lchild->parent = p;
    }
    pRchild->lchild = p;
    p->parent = pRchild;
}
// 移植节点
 void transplant(Node* oldNode, Node* newNode); // 移植节点

参见二叉搜索树 里的具体实现。
http://blog.youkuaiyun.com/lmx2014001/article/details/52072784
双旋转
case2 和 case3 属于对称情况,下面分析 case 2:(节点k1 不符合平衡二叉树的性质)
先以节点 k2 = k1-> lchild为研究对象进行一次 右右情况的旋转,singRotateRight(Node *p),将问题转化为 case1,然后在case1 情况下,再经过左左情况的旋转 singRotateLeft(k1)。
case2 左左情况:

void avlTree::doubleRotateLR(Node *p) // case2:左右情况
{
    this->singRotateRight(p->lchild);
    this->singRotateLeft(p);
}
void avlTree::doubleRotateRL(Node *p) // case3:右左情况
{
    this->singRotateLeft(p->rchild);
    this->singRotateRight(p);
}

插入节点

插入节点分两步:
第一步,和普通的二叉排序树相同的步骤插入;
第二步,从插入新节点的父亲开始,沿着走向根节点的简单路径,不断检查是否满足平衡二叉树的性质。
在描述具体检测步骤之前,先引入 任意节点 p 的平衡因子,即该节点 p 的左子树高度与右子树高度之差,具体实现如下:
易知 p>getBFac() 可能的返回值为 2, 1, 0, -1, -2,当取值为 2 和 - 2时不满足平衡性质。

int Node::getBFac()
{
    return this->lchild->getHight() - this->rchild->getHight();
}

详细检查及恢复步骤:
1. 求得新插入节点 N 的的父亲结点 p 的平衡因子 p>getBFac() ,可能取值只可能为 1,0,-1,图解如下:
这里写图片描述
case1,2不会改变以 父亲结点 p 为根节点的子树的高度,因此不会破坏整棵树的平衡性质。不需要进一步检测!
case3,4使父亲结点 p 为根节点的子树的高度由 1 (只有一个根节点 p)增加到了 1,因此可能破坏整棵树的平衡性质。需要进行下一步检测。

  1. 新插入节点N的父亲节点 p 的平衡因子为 1 时,进一步检查 节点N 的祖父节点G,如果 G>getBFac() 为2 或者 -2 ,则找到问题节点,并对其进行旋转操作,不再继续向上检测问题节点;如果 G>getBFac()=0 时才可以确定新插入节点并没有影响整棵树的平衡性质,则停止检查,不需要维护了; G>getBFac() 为1 或者 -1, 则将可能出现的问题点上移。

  2. 不断重复步骤 2,直到不断上升的祖父节点 G 越过根节点变为 NULL。

void  avlTree::insertAvlNode(int newValue)
{
    Node *newNode = new Node(newValue);
    Node *p, *tempParent = new Node(), *tempGrandpa = new Node(), *tempuncle = new Node();
    int parentFac, GrandpaFac;
    if (this->getSize() == 0)
    {
        this->root = newNode;
    }
    else
    {
        p = this->root;
        while (p)
        {
            // 保存父亲结点,万一他的孩子为空,跳出循环后还可以翻出来
            tempParent = p;

            if (newValue > p->data)
            {
                p = p->rchild;
            }
            else
            {
                p = p->lchild;
            }
        }
        newNode->parent = tempParent;
        if (newValue > tempParent->data)
        {
            tempParent->rchild = newNode;
        }
        else
        {
            tempParent->lchild = newNode;
        }
        // 以上步骤各普通的二叉排序树插入节点一样
        parentFac = tempParent->getBFac();
        if (parentFac) // 新插入节点的父节点 tempParent 平衡因子为非零,则tempParent 的高度肯定增加了 1,由原先的 0 变为1 
        {
            int rFlag = 0; // 标记是否旋转,最多只需要旋转一次(doubleRotate 也算一次)
            tempGrandpa = tempParent->parent;
            while (tempGrandpa && !rFlag)
            {
                GrandpaFac = tempGrandpa->getBFac();
                if (!GrandpaFac)
                {
                    break;
                }
                if (GrandpaFac == 2) //左
                {
                    if (parentFac == 1) // 左左
                    {
                        singRotateLeft(tempGrandpa);
                    }
                    else // 左右
                    {
                        doubleRotateLR(tempGrandpa);
                    }
                    rFlag = 1;
                }
                if (GrandpaFac == -2) //右
                {
                    if (parentFac == 1) // 右左
                    {
                        singRotateLeft(tempGrandpa);
                    }
                    else // 右右
                    {
                        singRotateRight(tempGrandpa);
                    }
                    rFlag = 1;
                }
                if (!rFlag) // 把可能出现问题的节点往上移动
                {
                    tempParent = tempGrandpa;
                    parentFac = GrandpaFac;
                    tempGrandpa = tempParent->parent;
                }
            }
        }
    }
}

欢迎批评指正 !

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值