【C++】AVL树

简介: AVL树是一种自平衡二叉搜索树,由Georgy Adelson-Velsky和Evgenii Landis提出。它通过确保任意节点的两子树高度差不超过1来维持平衡,支持高效插入、删除和查找操作,时间复杂度为O(log n)。AVL树通过四种旋转操作(左旋、右旋、左-右旋、右-左旋)来恢复树的平衡状态,适用于需要频繁进行数据操作的场景。

前言:

前面讲到了平衡搜索树(BST),但是不能会出现极端情况,出现线性的结构,今天介绍自平衡二叉搜索树(AVL)

AVL树

AVL的介绍

AVL树是一种自平衡二叉搜索树:它以苏联科学家Georgy Adelson-VelskyEvgenii Landis的名字命名。

  • AVL树中,任何节点的两个子树的高度最多相差1,这保证了树的平衡性,从而使得树的操作(如插入、删除和查找)具有良好的最坏情况性能,时间复杂度为O(log n)

  • AVL树的旋转操作是其自平衡机制的核心,包括左旋、右旋、左-右旋和右-左旋。这些旋转调整节点的位置,减少子树的高度差,恢复树的平衡。

AVL树特性

  • 严格的平衡性:AVL树的每个节点的左右子树的高度差的绝对值不超过1,这保证了树的高度最小,从而使得操作(如插入、删除和查找)的时间复杂度保持在对数级别。
  • 旋转调整:当插入或删除操作导致节点的平衡因子超出允许范围时,AVL树会通过单旋转或双旋转来重新平衡树。
  • 高效的搜索、插入和删除操作:由于AVL树的高度平衡,这些操作的时间复杂度通常为O(log n),其中n是树中节点的数量。
  • 节点定义AVL树的节点通常包含数据、指向左右子节点的指针、指向父节点的指针以及记录子树高度差的平衡因子。
  • 高度记录:在AVL树中,每个节点还会记录其子树的高度,这有助于快速计算平衡因子并在需要时进行调整。
  • 适用场景AVL树适用于需要频繁进行数据插入、删除和查找操作的场景,尤其是在数据集合较大时,可以保持较高的效率。

AVL树的核心

AVL树是一种自平衡的二叉查找树,它通过旋转调整机制来维持树的平衡。当树在插入或删除操作后出现不平衡时,AVL树会执行以下四种旋转操作之一来恢复平衡:

  1. 单旋转
    • 左旋(LL型):当插入或删除操作导致某个节点的左子树的高度比右子树高2时,需要执行左旋操作。左旋操作涉及到将这个节点的右子树提升为新的根节点,并将原根节点作为左子节点挂接到新根节点上。
    • 右旋(RR型):与左旋相反,当某个节点的右子树的高度比左子树高2时,执行右旋操作,即将左子树提升为新的根节点。
  2. 双旋转
    • 左-右旋(LR型):当插入或删除操作导致某个节点的左子树的右子树的高度比左子树的右子树高时,先对左子树执行左旋,然后对原节点执行右旋。
    • 右-左旋(RL型):与左-右旋相反,当某个节点的右子树的左子树的高度比右子树的左子树高时,先对右子树执行右旋,然后对原节点执行左旋。

AVL插入实现

  • AVL树是基于BST结构,大主体就是BST
  • 问题的解决就是AVL的单旋和双旋以及需要旋转的条件。

AVL的存贮结构

  • 在这里面利用KV结构进行存储数据
  • 左右节点,还有一个父亲节点
  • 为了保持平衡定义_bf(平衡因子:balance factor)
template<class K, class V>
struct AVLNode
{
   
    pair<K, V> _kv;
    AVLNode<K, V>* _left;
    AVLNode<K, V>* _right;
    AVLNode<K, V>* _parent;
    int _bf;

    AVLNode(const pair<K, V>& kv)
        :_kv(kv)
        , _left(nullptr)
        , _right(nullptr)
        , _parent(nullptr)
        , _bf(0)
    {
   }
};
template<class K, class V>
class AVL
{
   
    typedef AVLNode<K, V> Node;
    private:
    Node* _root;
}

AVL的插入

  1. 如果根节点是nullptr时,进行特殊的判断,直接进行节点的创建
  2. 查找插入的位置,大于在右边,小于在左边
  3. 进行平衡因子的更新,左边插入,父亲节点-1,右边插入,父亲节点+1(可以反向操作)
  4. 在节点更新的时候就要进行判断,只有发大于左右节点不平衡的时候需要旋转
    • 左旋转 、右旋转、左右旋转、右左旋转(下面会详细介绍)
bool insert(const pair<K, V>& kv)
{
   
    //step 1
    if (_root == nullptr)
    {
   
        _root = new Node(kv);
        return true;
    }
    Node* parent = nullptr;
    Node* cur = _root;
    //step 2
    while (cur)
    {
   
        if (kv.first > cur->_kv.first)
        {
   
            parent = cur;
            cur = cur->_right;
        }
        else if (kv.first < cur->_kv.first)
        {
   
            parent = cur;
            cur = cur->_left;
        }
        else
        {
   
            //key 存在
            return false;
        }
    }

    cur = new Node(kv);
    if (kv.first > parent->_kv.first)
    {
   
        parent->_right = cur;
    }
    else
    {
   
        parent->_left = cur;
    }

    cur->_parent = parent;
    //step 3
    //更新平衡因子
    while (parent)
    {
   
        if (cur == parent->_left)
        {
   
            parent->_bf--;
        }
        else
        {
   
            parent->_bf++;
        }

        if (parent->_bf == 0)
        {
   
            break;
        }
        else if (parent->_bf == -1 || parent->_bf == 1)
        {
   
            cur = parent;
            parent = parent->_parent;
        }
        else if (parent->_bf == 2 || parent->_bf == -2)
        {
   
            if (cur->_bf == 1 && parent->_bf == 2)
            {
   
                RotateL(parent);
            }
            else if (cur->_bf == -1 && parent->_bf == -2)
            {
   
                RotateR(parent);
            }
            else if (cur->_bf == -1 && parent->_bf == 2)
            {
   
                RotateRL(parent);
            }
            else if (cur->_bf == 1 && parent->_bf == -2)
            {
   
                RotateLR(parent);
            }
            break;
        }
        else
        {
   
            assert(false);
        }    
    }

AVL左旋

AVL树左旋的条件是:

  1. 当前节点的平衡因子(右子树的高度减去左子树的高度)为2,这意味着当前节点的左子树的高度至少比右子树高2。
  2. 当前节点的右子树必须是一个有效的AVL子树,即它的两个子树的高度差不超过1。
  3. 当前节点的左子树的右子节点(即当前节点的孙子节点)不为空
  • b节点摘下,放在30这个节点的右边
  • 将30这个整体放在60的左边
  • 更改父亲节点的关系,同时要注意的是B子树不为空,如果为空,就不要更新父亲节点。
void RotateL(Node* parent)
{
   
    Node* cur = parent->_right;
    Node* curleft = cur->_left;

    parent->_right = curleft;
    if (curleft)
    {
   
        curleft->_parent = parent;
    }

    cur->_left = parent;

    Node* ppnode = parent->_parent;

    parent->_parent = cur;

    if (ppnode == nullptr)
    {
   
        _root = cur;
        cur->_parent = nullptr;
    }
    else
    {
   
        if (ppnode->_left == parent)
        {
   
            ppnode->_left = cur;
        }
        else
        {
   
            ppnode->_right = cur;
        }

        cur->_parent = ppnode;
    }

    parent->_bf = cur->_bf = 0;
}

AVL右旋

AVL树进行右旋操作的条件

  1. 当一个节点的平衡因子(BF)为-2时,表示该节点的左子树的高度至少比右子树高2,这违反了AVL树的平衡性要求。
  2. 同时,该节点的左子节点的平衡因子必须为-1,这意味着左子节点的右子树存在,且其高度至少为1。

右旋的操作和左旋的操作成镜像

void RotateR(Node* parent)
{
   
    Node* cur = parent->_left;
    Node* curright = cur->_right;

    parent->_left = curright;
    if (curright)
    {
   
        curright->_parent = parent;
    }

    cur->_right = parent;
    Node* ppnode = parent->_parent;
    parent->_parent = cur;

    if (ppnode == nullptr)
    {
   
        _root = cur;
        cur->_parent = nullptr;
    }
    else
    {
   
        if (ppnode->_left == parent)
        {
   
            ppnode->_left = cur;
        }
        else
        {
   
            ppnode->_right = cur;
        }
        cur->_parent = ppnode;
    }

    cur->_bf = parent->_bf = 0;

}

AVL左-右旋

  1. 首先,找到需要旋转的节点,该节点的左子树的左子树高度大于右子树高度,导致平衡因子绝对值为2。
  2. 对该节点的左子树执行左旋操作,这会使得原节点成为左子树新的右子节点,并且原节点的左子树的右子节点成为新的左子树根节点。
  3. 接着,对原节点执行右旋操作,这会使得原节点成为整个子树的新根节点,并且原节点的左子节点(即之前左子树的右子节点)成为新根节点的左子节点。
  4. 旋转后,更新所有相关节点的父节点指针,并重新计算平衡因子,确保树重新平衡。
void RotateLR(Node* parent)
{
   
    Node* cur = parent->_left;
    Node* curright = cur->_right;
    int bf = curright->_bf;

    RotateL(parent->_left);
    RotateR(parent);

    if (bf == 0)
    {
   
        curright->_bf = 0;
        parent->_bf = 0;
        cur->_bf = 0;
    }
    else if (bf == 1)
    {
   
        parent->_bf = 0;
        cur->_bf = -1;
        curright->_bf = 0;
    }
    else if (bf == -1)
    {
   
        parent->_bf = 1;
        cur->_bf = 0;
        curright->_bf = 0;
    }
    else
    {
   
        assert(false);
    }
}

AVL右-左旋

  1. 首先对该节点的左子节点进行右旋操作,这会使得原本的左子节点成为新的根节点,而原来的右子节点成为左子节点的右孩子。
  2. 接着对原来的节点(现在是右子节点的左孩子)进行左旋操作,这会使得原本的右子节点成为新的根节点,原来的节点成为右子节点的左孩子。
  3. 注意更新三者的关系
void RotateRL(Node* parent)
{
   
    Node* cur = parent->_right;
    Node* curleft = cur->_left;
    int bf = curleft->_bf;

    RotateR(parent->_right);
    RotateL(parent);

    //解偶
    if (bf == 0)
    {
   
        parent->_bf = 0;
        cur->_bf = 0;
        curleft->_bf = 0;
    }
    else if (bf == -1)
    {
   
        parent->_bf = 0;
        cur->_bf = 1;
        curleft->_bf = 0;
    }
    else if (bf == 1)
    {
   
        cur->_bf = 0;
        parent->_bf = -1;
        curleft->_bf = 0;
    }
    else
    {
   
        assert(false);
    }
}

AVL树的检查

  • 根据AVL的特性,左右子树之间的相差的值小于2.
  • 这就需要进行根节点的高度的计算。
//计算高度
int Height(Node* root)
{
   
    if (root == nullptr)
    {
   
        return 0;
    }

    int leftHeight = Height(root->_left);
    int rightHeight = Height(root->_right);

    return max(leftHeight, rightHeight) + 1;

}
//判断平衡
bool  is_balance(Node* root)
{
   
    if (root == nullptr)
        return true;

    int leftHeight = Height(root->_left);
    int rightHeight = Height(root->_right);

    if (rightHeight - leftHeight != root->_bf)
    {
   
        cout << "balance id abnormal" << endl;
        return false;
    }

    return abs(leftHeight - rightHeight) < 2 && is_balance(root->_left) &&                                  is_balance(root->_right);
}

源码

#pragma once


#include<vector>
#include<iostream>
#include<utility>
#include<assert.h>

using namespace std;

namespace AVL
{
   
    template<class K, class V>
    struct AVLNode
    {
   
        pair<K, V> _kv;
        AVLNode<K, V>* _left;
        AVLNode<K, V>* _right;
        AVLNode<K, V>* _parent;
        int _bf;

        AVLNode(const pair<K, V>& kv)
            :_kv(kv)
            , _left(nullptr)
            , _right(nullptr)
            , _parent(nullptr)
            , _bf(0)
        {
   }
    };

    template<class K, class V>
    class AVL
    {
   
        typedef AVLNode<K, V> Node;
    public:
        AVL()
            :_root(nullptr)
        {
   }

        bool 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)
                {
   
                    parent = cur;
                    cur = cur->_right;
                }
                else if (kv.first < cur->_kv.first)
                {
   
                    parent = cur;
                    cur = cur->_left;
                }
                else
                {
   
                    //key 存在
                    return false;
                }
            }

            cur = new Node(kv);
            if (kv.first > parent->_kv.first)
            {
   
                parent->_right = cur;
            }
            else
            {
   
                parent->_left = cur;
            }

            cur->_parent = parent;

            //更新平衡因子
            while (parent)
            {
   
                if (cur == parent->_left)
                {
   
                    parent->_bf--;
                }
                else
                {
   
                    parent->_bf++;
                }

                if (parent->_bf == 0)
                {
   
                    break;
                }
                else if (parent->_bf == -1 || parent->_bf == 1)
                {
   
                    cur = parent;
                    parent = parent->_parent;
                }
                else if (parent->_bf == 2 || parent->_bf == -2)
                {
   
                    if (cur->_bf == 1 && parent->_bf == 2)
                    {
   
                        RotateL(parent);
                    }
                    else if (cur->_bf == -1 && parent->_bf == -2)
                    {
   
                        RotateR(parent);
                    }
                    else if (cur->_bf == -1 && parent->_bf == 2)
                    {
   
                        RotateRL(parent);
                    }
                    else if (cur->_bf == 1 && parent->_bf == -2)
                    {
   
                        RotateLR(parent);
                    }
                    break;
                }
                else
                {
   
                    assert(false);
                }    
            }

            return true;
        }


        int Height(Node* root)
         {
   
            if (root == nullptr)
            {
   
                return 0;
            }

            int leftHeight = Height(root->_left);
            int rightHeight = Height(root->_right);

            return max(leftHeight, rightHeight) + 1;

        }

        bool is_balance()
        {
   
            return is_balance(_root);
        }



        bool  is_balance(Node* root)
        {
   
            if (root == nullptr)
                return true;

            int leftHeight = Height(root->_left);
            int rightHeight = Height(root->_right);

            if (rightHeight - leftHeight != root->_bf)
            {
   
                cout << "balance id abnormal" << endl;
                return false;
            }

            return abs(leftHeight - rightHeight) < 2 && is_balance(root->_left) && is_balance(root->_right);
        }

        void RotateLR(Node* parent)
        {
   
            Node* cur = parent->_left;
            Node* curright = cur->_right;
            int bf = curright->_bf;

            RotateL(parent->_left);
            RotateR(parent);

            if (bf == 0)
            {
   
                curright->_bf = 0;
                parent->_bf = 0;
                cur->_bf = 0;
            }
            else if (bf == 1)
            {
   
                parent->_bf = 0;
                cur->_bf = -1;
                curright->_bf = 0;
            }
            else if (bf == -1)
            {
   
                parent->_bf = 1;
                cur->_bf = 0;
                curright->_bf = 0;
            }
            else
            {
   
                assert(false);
            }
        }

        void RotateRL(Node* parent)
        {
   
            Node* cur = parent->_right;
            Node* curleft = cur->_left;
            int bf = curleft->_bf;

            RotateR(parent->_right);
            RotateL(parent);

            //解偶
            if (bf == 0)
            {
   
                parent->_bf = 0;
                cur->_bf = 0;
                curleft->_bf = 0;
            }
            else if (bf == -1)
            {
   
                parent->_bf = 0;
                cur->_bf = 1;
                curleft->_bf = 0;
            }
            else if (bf == 1)
            {
   
                cur->_bf = 0;
                parent->_bf = -1;
                curleft->_bf = 0;
            }
            else
            {
   
                assert(false);
            }
        }


        void RotateR(Node* parent)
        {
   
            Node* cur = parent->_left;
            Node* curright = cur->_right;

            parent->_left = curright;
            if (curright)
            {
   
                curright->_parent = parent;
            }

            cur->_right = parent;
            Node* ppnode = parent->_parent;
            parent->_parent = cur;

            if (ppnode == nullptr)
            {
   
                _root = cur;
                cur->_parent = nullptr;
            }
            else
            {
   
                if (ppnode->_left == parent)
                {
   
                    ppnode->_left = cur;
                }
                else
                {
   
                    ppnode->_right = cur;
                }
                cur->_parent = ppnode;
            }

            cur->_bf = parent->_bf = 0;

        }


        void RotateL(Node* parent)
        {
   
            Node* cur = parent->_right;
            Node* curleft = cur->_left;

            parent->_right = curleft;
            if (curleft)
            {
   
                curleft->_parent = parent;
            }

            cur->_left = parent;

            Node* ppnode = parent->_parent;

            parent->_parent = cur;

            if (ppnode == nullptr)
            {
   
                _root = cur;
                cur->_parent = nullptr;
            }
            else
            {
   
                if (ppnode->_left == parent)
                {
   
                    ppnode->_left = cur;
                }
                else
                {
   
                    ppnode->_right = cur;
                }

                cur->_parent = ppnode;
            }

            parent->_bf = cur->_bf = 0;
        }

    private:
        Node* _root;
    };
}

cur;
}
cur->_parent = ppnode;
}

        cur->_bf = parent->_bf = 0;

    }


    void RotateL(Node* parent)
    {
        Node* cur = parent->_right;
        Node* curleft = cur->_left;

        parent->_right = curleft;
        if (curleft)
        {
            curleft->_parent = parent;
        }

        cur->_left = parent;

        Node* ppnode = parent->_parent;

        parent->_parent = cur;

        if (ppnode == nullptr)
        {
            _root = cur;
            cur->_parent = nullptr;
        }
        else
        {
            if (ppnode->_left == parent)
            {
                ppnode->_left = cur;
            }
            else
            {
                ppnode->_right = cur;
            }

            cur->_parent = ppnode;
        }

        parent->_bf = cur->_bf = 0;
    }

private:
    Node* _root;
};

}
~~~

目录
相关文章
|
3月前
|
存储 C++
【C++】AVL树
AVL树是一种自平衡二叉搜索树:它以苏联科学家Georgy Adelson-Velsky和Evgenii Landis的名字命名。
36 2
|
4月前
|
C++ 容器
【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——AVL树
【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——AVL树
46 5
|
5月前
|
C++
【C++】手撕AVL树(下)
【C++】手撕AVL树(下)
60 1
|
5月前
|
算法 测试技术 C++
【C++高阶】掌握AVL树:构建与维护平衡二叉搜索树的艺术
【C++高阶】掌握AVL树:构建与维护平衡二叉搜索树的艺术
42 2
|
5月前
|
Java C++ Python
【C++】手撕AVL树(上)
【C++】手撕AVL树(上)
58 0
|
6月前
|
C++
【c++】avl树
【c++】avl树
44 0
|
29天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
50 2
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
103 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
90 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
108 4