C++(第十五篇):AVLTree - 平衡二叉搜索树(介绍、实现)

博客主页:Morning_Yang丶
欢迎关注点赞收藏⭐️留言
本文所属专栏:【C++拒绝从入门到跑路】
作者水平有限,如果发现错误,敬请指正!感谢感谢!

文章目录

    • 前言
  • 一、AVL树
    • 1.1 AVL树的概念
    • 1.2 AVL树节点的定义
    • 1.3 AVL树 - 插入节点
      • ① 插入新节点
      • ② 更新树的平衡因子
      • ③ 根据更新后BF的情况,进行平衡化操作
        • 1️⃣ 右单旋 - 新节点插入较高左子树的最左侧,左边高
        • 2️⃣ 左单旋 - 新节点插入较高右子树的最右侧,右边高
        • 3️⃣ 右左双旋 - 新节点插入较高右子树的左侧
        • 4️⃣ 左右双旋 - 新节点插入较高左子树的右侧
    • 1.4 AVL树的验证
    • 1.5 AVL树 - 删除节点(了解)
    • 1.6 AVL树的性能
    • 1.7 整体代码

前言

二叉搜索树的插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的效率

但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度为O(N),因此 map、set 等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡二叉搜索树来实现。

C++(第十五篇):AVLTree - 平衡二叉搜索树(介绍、实现)_第1张图片

最优情况下,有 n 个结点的二叉搜索树为完全二叉树,查找效率为:O( l o g 2 N log_2N log2N)

最差情况下,有 n 个结点的二叉搜索树退化为单支树,查找效率为:O(N)


一、AVL树

1.1 AVL树的概念

平衡二叉搜索树(Self-balancing binary search tree),又称AVL树

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(超过1需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树,要么是空树,要么是具有以下性质的二叉搜索树:

  • 每个节点的左右子树高度之差(简称平衡因子 Balance Factor)的绝对值不超过 1 (-1/0/1)

    平衡因子 = 右子树的高度 - 左子树的高度:用来判断是否需要进行平衡操作(ps:平衡因子不是必须的,只是一种实现的表现方式,平衡因子的绝对值不超过1)

  • 每一个子树都是平衡二叉搜索树

C++(第十五篇):AVLTree - 平衡二叉搜索树(介绍、实现)_第2张图片

如果一棵二叉搜索树是高度平衡的,它就是AVL树。

有n个结点的AVL树,高度可保持在 l o g 2 N log_2N log2N,其搜索时间复杂度O( l o g 2 N log_2N log2N)。

思考:为什么左右子树高度差不规定成0呢?

因为在2、4等偶数个节点数的情况下,不可能做到左右高度相等

1.2 AVL树节点的定义

AVL树节点是一个三叉链结构,除了指向左右孩子的指针,还有一个指向其父亲的指针,数据域是键值对,即pair对象,还引入了平衡因子,用来判断是否需要进行平衡操作。

节点结构:

三叉链 + 平衡因子 + pair

// AVL树节点的定义(KV模型)
template<class K, class V>
struct AVLTreeNode
{
   
    AVLTreeNode<K, V>* _left;
    AVLTreeNode<K, V>* _right;
    AVLTreeNode<K, V>* _parent;

    int _bf;//平衡因子
    pair<K, V> _kv;//数据
    AVLTreeNode(const pair<K,V>& kv = pair<K, V>())
        : _left(nullptr)
            , _right(nullptr)
            , _parent(nullptr)
            , _bf(0)
            , _kv(kv)
        {
    }
};

// AVL树的定义(KV模型)
template<class K, class V>
class AVLTree
{
   
	typedef AVLTreeNode<K, V> Node;

private:
	Node* _root;

public:
	// 成员函数
}

1.3 AVL树 - 插入节点

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为3步:

  1. 插入新节点
  2. 更新树的平衡因子
  3. 根据更新后树的平衡因子的情况,来控制树的平衡(旋转操作)

① 插入新节点

和二叉搜索树插入方式一样,先查找,再插入。

// 插入节点
bool AVLTree::Insert(const pair<K, V>& kv)
{
   
    // 如果树为空,则直接插入节点
    if (_root == nullptr)
    {
   
        _root = new Node(kv);
        return true;
    }

    // 如果树不为空,找到适合插入节点的空位置
    Node* parent = nullptr;  // 记录当前节点的父亲
    Node* cur = _root;       // 记录当前节点
    while (cur)
    {
   
        if(kv.first > cur->_kv.first) // 插入节点键值k大于当前节点
        {
   
            parent = cur;
            cur = cur->_right;
        }
        else if(kv.first < cur->_kv.first) // 插入节点键值k小于当前节点
        {
    
            parent = cur;
            cur = cur->_left;
        }
        else // 插入节点键值k等于当前节点
        {
   
            return false;
        }
    }
    // while循环结束,说明找到适合插入节点的空位置了

    // 插入新节点
    cur = new Node(kv); // 申请新节点
    // 判断当前节点是父亲的左孩子还是右孩子
    if (cur->_kv.first > parent->_kv.first)
    {
   
        parent->_right = cur;
        cur->_parent = parent;
    }
    else
    {
   
        parent->_left = cur;
        cur->_parent = parent;
    }

    //...................................
    // 这些写更新平衡因子,和控制树的平衡的代码
    //...................................
    
    // 插入成功
    return true;
}

② 更新树的平衡因子

一个节点的平衡因子是否更新取决于他的左右子树的高度是否变化,插入「新节点」,从该节点到根所经分支上的所有节点(即祖先节点)的平衡因子都有可能会受到影响,根据不同情况,更新它们的平衡因子:

  • 如果插入在「新节点父亲」的右边,父亲的平衡因子++( _bf++
  • 如果插入在「新节点父亲」的左边,父亲的平衡因子–( _bf--

新节点父亲」的平衡因子更新以后,又会分为 3 种情况:

1、如果更新以后,平衡因子是 1 或者 -1(则之前一定为 0),说明父亲所在子树高度变了,需要继续往上更新。(最坏情况:往上一直更新到根节点C++(第十五篇):AVLTree - 平衡二叉搜索树(介绍、实现)_第3张图片

2、如果更新以后,平衡因子是 0(则之前一定为 1 或者 -1),说明父亲所在子树高度没变(因为把矮的那边给填补上了),不需要继续往上更新C++(第十五篇):AVLTree - 平衡二叉搜索树(介绍、实现)_第4张图片

3、如果更新以后,平衡因子是 2 或者 -2,说明父亲所在子树出现了不平衡,需要旋转处理,让它平衡。C++(第十五篇):AVLTree - 平衡二叉搜索树(介绍、实现)_第5张图片

代码如下:

//控制平衡,更新平衡因子
while (parent) // 最坏情况:更新到根节点
{
   
    // 更新新节点父亲的平衡因子
    if (cur == parent->_left) // 新节点插入在父亲的左边
    {
   
        parent->_bf--;
    }
    else // 新节点插入在父亲的右边
    {
   
        parent->_bf++;
    }

    // 检查新节点父亲的平衡因子
    // 1、父亲所在子树高度变了,需要继续往上更新
    if (parent->_bf == 1 || parent->_bf == -1)
    {
   
        cur = parent;
        parent = cur->_parent;
    }
    // 2、父亲所在子树高度没变,不用继续往上更新
    else if (parent->_bf == 0)
    {
   
        break;
    }
    // 3、父亲所在子树出现了不平衡,需要旋转处理
    else if (parent->_bf == 2 || parent->_bf == -2)
    {
   
        // 这里写对树进行平衡化操作,旋转处理的代码,分为4种情况:
        
        /*................................................*/
        if (parent->_bf == 2)//等于2,说明是右子树插入新节点
        {
   
            if (cur->_bf == 1

你可能感兴趣的:(【C++拒绝从入门到跑路】,数据结构,c++,算法,数据结构,开发语言)