自平衡二叉排序树(BBST)

二叉排序树的最坏情况

二叉排序树,依据左子树的值都小于根节点,右子树的值都大于根节点,如果一组数据是降序的有序序列,这样就会创建出一个左斜树,其实就成为了一个单链表。

左斜树

退化为单链表之后,查询的时间复杂度就降低到O(n),这就是最坏时间复杂度。针对这种情况该如何解决呢,聪明的人想到了一种使二叉排序树不单偏的方法,通过平衡二叉树使二叉树左右两边的高度不至于差距太大。

AVL树

AVL树是计算机科学中最早被发明的自平衡二叉查找树。AVL树得名于它的发明者 格奥尔吉·阿杰尔松-韦利斯基(Georgii Adelson-Velskii)和叶夫根尼·兰迪斯(Yevgeny Mikhaylovich Landis),分别取他们名字的首字母组合而来,这一数据结构是在1962年被公开。

特性

  • 如果一棵二叉树是AVL树,那它的根节点的左右子树也是AVL树,且左子树高度减右子树高度的绝对值小于等于1
  • 对树的各种操作的时间复杂度始终保持在O(logn)级别

根据上面的特性,可得出AVL树的平衡因子是 height(leftsubtree) - height(rightsubtree)

节点

由于AVL树的平衡因子是需要计算左右子树高度的差值,因此我们需要在原二叉树节点数据结构的基础之上增加高度(height)变量。

typedef struct BAVLTreeNode {
        int m_Data;
        int m_Height;
        BAVLTreeNode *m_LeftChild;
        BAVLTreeNode *m_RightChild;

        explicit BAVLTreeNode(int value):m_Data(value), m_Height(0), m_LeftChild(nullptr), m_RightChild(nullptr) {}
} BAVLTreeNode, *BAVLNode;

基本要素

节点的高度获取和节点高度差值计算是创建及调整一棵AVL的关键,下面就先完成这两块的代码。

节点高度
int BAVLTree::Height(BAVLNode node)
{
    if (node == nullptr)
    {
        // https://zh.wikipedia.org/wiki/%E4%BA%8C%E5%8F%89%E6%A0%91
        // 对于节点的高度,维基百科上是以边为计算依据,所以空节点的高度为-1,叶节点的高度为0
        // 而leetcode和一些算法书上是以节点为计算依据,所以空节点的高度是0,叶节点的高度是1
        // 这里就取了leetcode和一些算法书上的计算标准
        return 0;
    }
    int height = node->m_Height;
    return height;
}

void BAVLTree::UpdateHeight(BAVLNode node)
{
    int height = std::max(Height(node->m_LeftChild), Height(node->m_RightChild)) + 1;
    node->m_Height = height;
}
高度差值

高度差值就是平衡因子的计算。

int BAVLTree::BalanceFactor(BAVLNode node)
{
    if (node == nullptr)
    {
        return 0;
    }
    // 平衡因子等于左子树高度减右子树高度
    return Height(node->m_LeftChild) - Height(node->m_RightChild);
}

插入节点

AVL树是二叉排序树的一种变形,所以在插入节点的基础部分上和二叉排序树是一样,都需要先跟已有的节点进行大小比较,小的放左边,大的放右边。在这些操作完成之后,就需要进行自平衡操作,显示递归更新所有节点的高度,然后进行整棵树的平衡调整。

BAVLTree::BAVLNode BAVLTree::InsertNode(BAVLNode node, int value)
{
    if (node == nullptr)
    {
        node = new BAVLTreeNode(value);
        return node;
    }
    if (node->m_Data > value)
    {
        node->m_LeftChild = InsertNode(node->m_LeftChild, value);
    }
    else if (node->m_Data < value) {
        node->m_RightChild = InsertNode(node->m_RightChild, value);
    }
    else
    {
        return node;
    }
    
    // 更新节点高度
    UpdateHeight(node);
    // 调整失衡节点及其子树
    node = AdjustBalance(node);
    
    return node;
}

调整树的平衡的依据是平衡因子,根据平衡因子计算的结果来确定当前失衡的节点是处于什么情况。

失衡节点

如何找到失衡节点?

从插入节点开始向上寻找,如果平衡因子的绝对值大于1,则是失衡节点。

  • LL和RR型,这两种类型刚好是镜像对称的。

    LL和RR

    • 从新插节点开始向上查找,找到失衡节点之后,从失衡节点开始向下查找两个节点,如果这两个节点都位于同一侧,就判定是左左型或右右型。
    • LL型是左偏树,其实就是左侧太高导致整棵树向一侧偏斜,为了避免二叉排序树由于一侧太高而出现退化为链表的情况,就需要找到左侧子树的失衡节点,然后以失衡节点为基点向右旋转,这样就能使整棵树达到AVL树的要求。
    • RR型是右偏树,旋转原理同LL型,但它需要向左旋转。
  • LR和RL型

    LR和RL型

    • LR型也是左侧子树高于右侧子树,但同LL型不同的是它是失衡节点的左侧节点下面只有右侧节点,这样在调整时,只得先调整下部分即节点2和节点4,先以节点2为基点向左旋转,旋转完成之后就是一棵LL型的失衡树,再按照LL型的旋转逻辑向右旋转就行。
    • RL型是右侧子树高于左侧子树,其调整同LR型,但由于是右边偏斜,所以需要先调整节点7和节点6,而且是向右旋转,使其形成一棵RR型失衡树,然后再向左旋转调整。
失衡类型旋转方式
LL型右旋
RR型左旋
LR型先左旋再右旋
RL型先右旋再左旋
旋转
左旋
BAVLTree::BAVLNode BAVLTree::LeftRotate(BAVLNode node)
{
    //  ●
    //    ●
    //      ●
    BAVLNode rChild = node->m_RightChild;
    BAVLNode rLeftChild = rChild->m_LeftChild;
    
    rChild->m_LeftChild = node;
    node->m_RightChild = rLeftChild;

    UpdateHeight(node);
    UpdateHeight(rChild);
    
    return rChild;
}
右旋
BAVLTree::BAVLNode BAVLTree::RightRotate(BAVLNode node)
{
    //     ●
    //   ●
    // ●
    BAVLNode lChild = node->m_LeftChild;
    BAVLNode lRightNode = lChild->m_RightChild;

    lChild->m_RightChild = node;
    node->m_LeftChild = lRightNode;

    UpdateHeight(node);
    UpdateHeight(lChild);
    
    return lChild;
}
调整
BAVLTree::BAVLNode BAVLTree::AdjustBalance(BAVLNode node)
{
    int factor = BalanceFactor(node);
    if (factor > 1) //左子树高度大于右子树高度,形成左偏树
    {
        int leftFactor = BalanceFactor(node->m_LeftChild);
        //再找节点的左子节点的平衡因子
        //     ● --- 该节点的右侧没有右子节点,所以平衡因子是2
        //    ●  --- 该节点如果有左子节点,无论是否有右子节点,都确定从该节点的父节点开始,是一棵左左型子树
        //   ● ●  
        if (leftFactor >= 0)//LL型
        {
            return RightRotate(node);
        } 
        //     ●
        //   ● --- 该节点只有右子节点,所以平衡因子是-1,形成LR型子树
        //     ●
        else //LR 型
        {
            // 以失衡节点的左子树为基点进行调整
            node->m_LeftChild = LeftRotate(node->m_LeftChild);
            return RightRotate(node);
        }        
    } 
    else if (factor < -1)// 右子树高度小于左子树高度,形成右偏树
    {
        int rightFactor = BalanceFactor(node->m_RightChild);
        //  ●  --- 该节点的右侧没有左子节点,所以平衡因子是2
        //    ● --- 该节点如果有右子节点,无论是否有左子节点,都确定从该节点的父节点开始,是一棵右右型子树
        //  ●   ●
        if (rightFactor <= 0)//RR型
        {
            return LeftRotate(node);
        } 
        //     ●
        //       ● --- 该节点只有左子节点,所以平衡因子是-1,形成RL型子树
        //     ●
        else  //RL型
        {
            // 以失衡节点的右子节点为基点进行调整
            node->m_RightChild = RightRotate(node->m_RightChild);
            return LeftRotate(node);
        }
    }
    return node;
}

删除节点

AVL树删除节点需要分两步,一是按照二叉排序树的方式进行节点删除,二是在删除节点之后,需要递归遍历整棵树查找失衡节点并通过调整使其恢复平衡。

删除
BAVLTree::BAVLNode BAVLTree::DeleteNode(BAVLNode node, int value)
{
    if (node == nullptr)
    {
        return nullptr;
    }

    if (node->m_Data > value)
    {
        node->m_LeftChild = DeleteNode(node->m_LeftChild, value);
    } 
    else if (node->m_Data < value)
    {
        node->m_RightChild = DeleteNode(node->m_RightChild, value);
    } 
    else {
        // 1、没有子树,
        // 2、仅有左子树
        // 3、仅有右子树
        if (node->m_LeftChild == nullptr || node->m_RightChild == nullptr)
        {
            BAVLNode child = node->m_LeftChild != nullptr?node->m_LeftChild:node->m_RightChild;
            if (child == nullptr)
            {
                delete node;
                return nullptr;
            } 
            else {
                delete node;
                node = child;
            }
        } 
        else // 有左右子树,这里就需要用 二叉排序树按中序遍历 得到的 前驱或后继的值 来替换当前被删除的节点,然后删除前驱或后继节点
        {
            // 这次使用后继节点来替换当前被删除的节点
            // 先找右子树的根节点
            BAVLNode tmpNode = node->m_RightChild;
            // 以tmpNode为基点再向左查找最终的左节点
            while (tmpNode->m_LeftChild)
            {
                tmpNode = tmpNode->m_LeftChild;
            }
            int tmpValue = tmpNode->m_Data;
            // 删除要替换被删除节点的节点
            node->m_RightChild = DeleteNode(node->m_RightChild, tmpNode->m_Data);
            node->m_Data = tmpValue;
        }
    }
    // 更新节点高度
    UpdateHeight(node);
    // 调整失衡的节点
    node = AdjustBalance(node);
    return node;
}

查找节点

由于AVL树也是一棵二叉排序树,查找节点也是一样的方式,这里就不再赘述。

测试代码

int main() {
    std::vector<int> vArr = {40, 36, 23, 14, 90, 44, 32, 78, 10, 9, 3, 20};
    BAVLTree *bTree = new BAVLTree();
    BAVLTree::BAVLTreeNode *root;
    std::vector<int>::iterator cbegin = vArr.begin();
    std::vector<int>::iterator cend = vArr.end();
    while(cbegin != cend)
    {
        root = bTree->InsertNode(root, *cbegin);
        cbegin++;
    }

    bTree->InorderTraversal(root);
    std::cout << std::endl;

    root = bTree->DeleteNode(root, 14);

    bTree->InorderTraversal(root);
    std::cout << std::endl;

    delete bTree;
    while (true)
    {
        /* code */
    }
}


//输出结果:
3  9  10  14  20  23  32  36  40  44  78  90  
3  9  10  20  23  32  36  40  44  78  90 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值