• 【二叉树进阶】AVLTree-平衡二叉搜索树


    1、AVL树

    1.1、AVL树的概念

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

    1. 它的左右子树都是AVL树
    2. 左右子树高度之差(简称平衡因子)的绝对值不超过1(高度差:-1/0/1)
      在这里插入图片描述
      如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在
      O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度O( l o g 2 n log_2 n log2n)。

    1.2 AVL树节点的定义

    AVL树的节点采用三叉链结构,其中包含指向左右子节点的指针和指向父亲的指针。数据存储在键值对中,使用pair对象表示。为了保持树的平衡,引入了平衡因子来判断是否需要进行平衡操作。

    // AVL树节点的定义(KV模型)
    template<class K, class V>
    struct AVLTreeNode
    {
    	pair<K, V> _kv;  // 键值对
    	int _bf;         // 平衡因子(balance factor) = 右子树高度 - 左子树高度
    	AVLTreeNode<K, V>* _left;
    	AVLTreeNode<K, V>* _right;
    	AVLTreeNode<K, V>* _parent; // 双亲指针
    
    	// 构造函数
    	AVLTreeNode(const pair<K, V>& kv)
    		:_kv(kv)
    		,_bf(0)
    		,_left(nullptr)
    		,_right(nullptr)
    		,_parent(nullptr)
    	{}
    };
    
    // AVL树的定义(KV模型)
    template<class K, class V>
    class AVLTree
    {
    	typedef AVLTreeNode<K, V> Node;
    
    private:
    	Node* _root;
    
    public:
    	// 成员函数
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    1.3 AVL树 - 插入节点

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

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

    1.3.1 插入新节点

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

    // 插入节点
    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    1.3.2 更新树的平衡因子

    插入新节点后,该节点到根节点之间的所有祖先节点的平衡因子可能会受到影响。根据不同的情况,需要更新它们的平衡因子。

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

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

    1. 如果更新以后,平衡因子是 1 或者 -1(则之前一定为 0),说明父亲所在子树高度变了,需要继续往上更新。(最坏情况:往上一直更新到根节点)
      在这里插入图片描述
      2、如果更新以后,平衡因子是 0(则之前一定为 1 或者 -1),说明父亲所在子树高度没变(因为把矮的那边给填补上了),不需要继续往上更新。
      在这里插入图片描述
      3、如果更新以后,平衡因子是 2 或者 -2,说明父亲所在子树出现了不平衡,需要旋转处理,让它平衡。
      在这里插入图片描述
      代码如下:
    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种情况:
            
            /*................................................*/
            // 3.1、父节点的左边高,右边低,需要往右旋
            if (parent->_bf == -2 && cur->_bf == -1)
            {
                // 右单旋
                treeRotateRight(parent); 
            }
            
            // 3.2、父节点的右边高,左边低,需要往左旋
            else if (parent->_bf == 2 && cur->_bf == 1)
            {
                // 左单旋
                treeRotateLeft(parent); 
            }
            
            // 3.3、父节点的左边高,且父节点左孩子的右边高
            else if(parent->_bf == -2 && cur->_bf == 1)
            {
                // 左右双旋
                treeRotateLR(parent);
            }
            
            // 3.4、父节点的右边高,且父节点右孩子的左边高
            else if(parent->_bf == 2 && cur->_bf == -1)
            {
                // 右左双旋
                treeRotateRL(parent);
            }
            
            else // 只有上述4种情况,没有其它情况,所以这里直接报错处理
            {
                assert(false);
            }
            
            break; // 旋转完成,树已平衡,退出循环
            
            /*................................................*/
        }
        // 4、除了上述3种情况,平衡因子不可能有其它的值,报错处理
        else
        {
            assert(false);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    1.3.3 根据更新后BF的情况,进行平衡化操作

    如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为4种:

    旋转的本质:在遵循二叉搜索树的规则下,让左右均衡,降低整棵树的高度。

    该进行哪种旋转操作?– 引发旋转的路径是直线就是单旋,如果是折线就是双旋。

    👇注意:此处看到的树,可能是一颗完整的树,也可能是一颗子树。
    ① 右单旋 - 新节点插入较高左子树的最左侧
    将新的节点插入到了 parent 左孩子的左子树上,导致的不平衡的情况。
    在这里插入图片描述
    上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左子树高度增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能让其成为30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能让其成为60的左子树,旋转完成后,更新节点的平衡因子即可。
    引发右单旋的条件:

    1. 父亲左边高,右边低,所以要让父亲往右旋。
    2. parent 的平衡因子为 -2,parent 左孩子平衡因子为 -1,观察发现,平衡因子都是负数,说明是左边高,也说明了==【引发旋转的路径是一条直线】==,所以我们要右旋操作。
      右单旋操作:
    3. 让 subL 的右子树 subLR 成为 parent 的左子树(因为 subLR 的右子树根节点值大于30,小于60)
    4. 让 parent 成为 subL 的右子树(因为60大于30)
    5. 让 subL 变成这个子树的根
      这一步操作前需要先判断下:parent 是根节点,还是一个普通子树
      如果是根节点,则更新 subL 为新的根
      如果是普通子树(可能是某个节点的左子树,也可能是右子树,这里需要判断下),然后更新 subL 为这个子树的根节点
    6. 根据树的结构,更新 parent 和 subL 的平衡因子为0
      在旋转过程中,更新双亲指针的指向,有以下几种情况需要考虑:
    7. subL 的右子树 subLR 可能存在,也可能为空。(当不为空时才更新 subL 右子树 subLR 的双亲指针指向)
    8. 旋转完成后,subL 的双亲节点,可能是空,也可能是 parent 原先的父节点。(所以更新 subL 的双亲指针前需要判断下)
      代码如下:

    总的来说,就是依次调整 subLR、parent、subL 的位置和双亲指针的指向。

    // 右单旋
    void treeRotateRight(Node* parent)
    {
        // subL:parent的左孩子
        // subLR:parent左孩子的右孩子
        Node* subL = parent->_left;
        Node* subLR = parent->_left->_right;
    
        // 1、让subL的右子树subLR成为parent的左子树
        parent->_left = subLR;
        // 1.1、如果subLR不为空
        if (subLR)
        {
            subLR->_parent = parent; // 更新subLR的双亲指针,指向parent
        }
    
        // 2、让parent成为subL的右子树
        subL->_right = parent;
    
        // 2.1、记录下parent的父节点
        Node* ppNode = parent->_parent;
    
        // 2.2、更新parent的双亲指针,指向subL
        parent->_parent = subL;
    
        // 2.3、判断parent是不是根节点
        // 是根节点
        if (parent == _root)
        {
            _root = subL;            // 更新subL为新的根
            subL->_parent = nullptr; // 更新subL的双亲指针,指向空
        }
        // 不是根节点,就是一个普通子树
        else
        {
            // 判断parent原先是左孩子还是右孩子
            if (ppNode->_left == parent)
            {
                ppNode->_left = subL; // parent原先的双亲节点接管subL,subL为这个子树的根
            }
            else
            {
                ppNode->_right = subL;
            }
    
            subL->_parent = ppNode; // 更新subL的双亲指针
        }
    
        // 根据调整后的结构更新parent和subL的平衡因子
        parent->_bf = subL->_bf = 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    ② 左单旋 - 新节点插入较高右子树的最右侧
    将新的节点插入到了 parent 右孩子的右子树上,导致的不平衡的情况。
    在这里插入图片描述

    引发左单旋的条件:

    父亲右边高,左边低,所以要让父亲往左旋。
    parent 的平衡因子为 2,parent 右孩子平衡因子为 1,观察发现,平衡因子都是正数,说明是右边高,也说明了==【引发旋转的路径是一条直线】==,所以我们要左旋操作。

    左单旋操作:

    1. 让 subR 的左子树 subRL 成为 parent 的右子树(因为 subRL 的左子树根节点值大于30,小于60)
    2. 让 parent 成为 subR 的左子树(因为30小于60)
    3. 让 subR 变成这个子树的根
      这一步操作前需要先判断下:parent 是根节点,还是一个普通子树
      如果是根节点,则更新 subR 为新的根
      如果是普通子树(可能是某个节点的左子树,也可能是右子树,这里需要判断下),然后更新 subR 为这个子树的根节点
      根据树的结构,更新 parent 和 subR 的平衡因子为0

    在旋转过程中,更新双亲指针的指向,有以下几种情况需要考虑:
    subR 的左子树 subRL 可能存在,也可能为空。(当不为空时才更新 subR 左子树 subRL 的双亲指针指向)
    旋转完成后,subR 的双亲节点,可能是空,也可能是 parent 原先的父节点。(所以更新 subR 的双亲指针前需要判断下)
    代码如下:

    总的来说,就是依次调整 subRL、parent、subR 的位置和双亲指针的指向。

    // 左单旋
    void treeRotateLeft(Node* parent)
    {
        // subR:父亲的右孩子
        // subRL:父亲的右孩子的左孩子(大于父亲,小于subR)
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
    
        // 1、让subRL成为父亲的右子树
        parent->_right = subRL;
        // 如果subRL不为空
        if (subRL)
        {
            subRL->_parent = parent; // 更新subRL双亲指针,指向parent
        }
    
        // 2、让parent成为subR的左子树
        subR->_left = parent;
    
        // 2.1、先记录下parent的双亲节点
        Node* ppNode = parent->_parent;
    
        // 2.2、更新parent双亲指针的指向
        parent->_parent = subR;
    
        // 2.3、判断parent是不是根节点
        // 是根节点
        if (parent == _root)
        {
            _root = subR;            // subR为新的根
            subR->_parent = nullptr; // subR双亲指针指向空
        }
        // 不是根节点,就是一个普通子树
        else
        {
            // 判断parent原先是左孩子还是右孩子
            if (ppNode->_left == parent)
            {
                ppNode->_left = subR; // parent原先的双亲节点接管subR,subR为这个子树的根
            }
            else
            {
                ppNode->_right = subR;
            }
    
            subR->_parent = ppNode; // 更新subR的双亲指针
        }
    
        // 根据树的结构,更新parent和subR的平衡因子
        parent->_bf = subR->_bf = 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    ③ 左右双旋 - 新节点插入较高左子树的右侧
    将新的节点插入到了 parent 左孩子的右子树上,导致的不平衡的情况。
    这时我们需要的是先对 parent 的右孩子进行一次左旋,再对 parent 进行一次右旋。

    这里可以观察到一个现象: 节点60的左右子树被分走了,左子树最终成了30的右子树,右子树最终成了90的左子树。
    在这里插入图片描述
    将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再
    考虑平衡因子的更新。

    // 旋转之前,60的平衡因子可能是-1/0/1,旋转完成之后,根据情况对其他节点的平衡因子进
    行调整
    void _RotateLR(PNode pParent)
    {
    PNode pSubL = pParent->_pLeft;
    PNode pSubLR = pSubL->_pRight;
     
      // 旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节
    点的平衡因子
    int bf = pSubLR->_bf;
     
      // 先对30进行左单旋
    _RotateL(pParent->_pLeft);
     
      // 再对90进行右单旋
    _RotateR(pParent);
    if(1 == bf)
    pSubL->_bf = -1;
    else if(-1 == bf)
    pParent->_bf = 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    *** 新节点插入较高右子树的左侧—右左:先右单旋再左单旋***
    在这里插入图片描述
    参考右左双旋。
    总结:
    假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑

    1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR
      当pSubR的平衡因子为1时,执行左单旋
      当pSubR的平衡因子为-1时,执行右左双旋
    2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL
      当pSubL的平衡因子为-1是,执行右单旋
      当pSubL的平衡因子为1时,执行左右双旋
      旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。

    2 AVL树的验证

    AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

    1. 验证其为二叉搜索树
      如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
    2. 验证其为平衡树
      每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
      节点的平衡因子是否计算正确
    int _Height(PNode pRoot);
    bool _IsBalanceTree(PNode pRoot)
    {
    // 空树也是AVL树
    if (nullptr == pRoot) return true;
     
    // 计算pRoot节点的平衡因子:即pRoot左右子树的高度差
    int leftHeight = _Height(pRoot->_pLeft);
    int rightHeight = _Height(pRoot->_pRight);
    int diff = rightHeight - leftHeight;
    // 如果计算出的平衡因子与pRoot的平衡因子不相等,或者
    // pRoot平衡因子的绝对值超过1,则一定不是AVL树
    if (diff != pRoot->_bf || (diff > 1 || diff < -1))
    return false;
    // pRoot的左和右如果都是AVL树,则该树一定是AVL树
    return _IsBalanceTree(pRoot->_pLeft) && _IsBalanceTree(pRoot-
    
    >_pRight);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    AVL树的验证
    AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
    验证其是否为二叉搜索树
    如果中序遍历可以得到一个有序的序列,就说明为二叉搜索树。

    验证其是否为平衡树
    每个节点子树高度差的绝对值不超过1

    节点的平衡因子是否计算正确
    (1)首先写一个计算当前树高度的函数

    // 计算当前树的高度
    int Height(Node* root)
    {
        // 当前树为空,则高度为0
        if (root == nullptr)
            return 0;
    
        // 当前树不为空,计算左右子树的高度
        int leftHeight = Height(root->_left);
        int rightHeight = Height(root->_right);
    
        // 当前树的高度 = 左右子树中高度最大的那个加1
        return max(leftHeight, rightHeight) + 1;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (2)检查AVL树是否平衡,思路一:自顶向下的暴力解法

    // 检查AVL树是否平衡,思路一
    bool IsBalance1()
    {
        return _IsBalance1(_root);
    }
    bool _IsBalance1(Node* root)
    {
        // 当前树为空,说明是平衡的
        if (root == nullptr)
            return true;
    
        // 当前树不为空,计算左右子树的高度
        int leftHeight = Height(root->_left);
        int rightHeight = Height(root->_right);
    
        if (rightHeight - leftHeight != root->_bf) // 检查当前树的平衡因子是否计算正确
        {
            cout << "平衡因子异常:" << root->_kv.first << endl;
        }
        
        // 左右子树高度相减的绝对值小于2,说明当前树是平衡的,则继续往下判断其它子树
        return abs(leftHeight - rightHeight) < 2
            && _IsBalance1(root->_left)
            && _IsBalance1(root->_right);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    (3)检查AVL树是否平衡,思路二:自底向上的高效解法(动态规划,前一个子问题的解,能够用于后一个问题求解)

    // 检查AVL树是否平衡,思路二
    bool IsBalance2()
    {
        return _IsBalance2(_root) != -1;
    }
    
    int _IsBalance2(Node* root)
    {
        // 先判断当前树的左、右子树是否平衡,再判断当前树是否平衡
        // 不平衡返回-1,平衡则返回当前树的高度
    
        // 当前树为空,返回高度0
        if (root == nullptr)
            return 0;
    
        // 当前树不为空,分别计算左右子树的高度
        int leftHeight = _IsBalance2(root->_left);
        int rightHeight = _IsBalance2(root->_right);
        
        if (rightHeight - leftHeight != root->_bf) // 检查当前树的平衡因子是否计算正确
        {
            cout << "平衡因子异常:" << root->_kv.first << endl;
        }
        
        // 左子树高度等于-1、右子树高度等于-1、左右子树高度差的绝对值大于1,说明当前树不平衡
        if (leftHeight == -1 || rightHeight == -1 || abs(leftHeight - rightHeight) > 1)
            return -1;
    
        // 运行到这里来了,说明当前树是平衡的,返回当前树的高度
        return max(leftHeight, rightHeight) + 1;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    2.1 AVL树 - 删除节点(了解)

    因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,如果出现不平衡树,进行旋转。只不过与二叉搜索树不同的是,AVL树删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。

    2.2 AVL树的性能

    AVL树是一棵绝对平衡的二叉搜索树,接近于完全二叉树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 O(log2N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

  • 相关阅读:
    SpringBatch适配不同数据库的两种方法
    选择题汇总1-2(括号里填的答案都是对的,不用管下面那个答案正确与错误,因为作者懒得删了)
    markdown文件中的外链图片上传到GitHub图床
    列表的嵌套--Python
    nodejs微信小程序+python+PHP-维斯公司财务管理系统的设计与实现-计算机毕业设计推荐
    EasyExcel的应用
    【翻译】一种减小运动伪影的新方法基于AS-LMS自适应滤波器的PPG信号
    手把手带你学python自动化测试(一)——自动化测试环境搭建
    避免项目进度延期,5大有效措施!
    GD32f103系列教程—(时钟篇)
  • 原文地址:https://blog.csdn.net/qq_51536567/article/details/133951044