AVL树(C++实现)

目录

AVL树概念

AVL树节点的定义

AVL树的插入

AVL树的旋转

新节点插入较高左子树的右侧---右右:左单旋

新节点插入较高左子树的左侧---左左:右单旋

新节点插入较高右子树的左侧---右左:先右单旋再左单旋

新节点插入较高左子树的右侧--左右:先左单旋再右单旋​编辑

AVL树的验证

AVL树的删除(了解)

AVL树的性能

项目文件


AVL树概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下

因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:

当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

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

❍ 它的左右子树都是AVL树

❍ 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在

$O(log_2 n)$,搜索时间复杂度O($log_2 n$)。

AVL树节点的定义

template<class K, class V>
struct AVLTreeNode{
    pair<K,V> _kv;
    AVLTreeNode<K, V>* _left;
    AVLTreeNode<K, V>* _right;
    AVLTreeNode<K, V>* _parent;
    int _bf;//balance factor
    
    AVLTreeNode(const pair<K, V>& kv)
    :_kv(kv),
    _left(nullptr),
    _right(nullptr),
    _parent(nullptr),
    _bf(0)
    {
        
    }
    
};

 

AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么

AVL树的插入过程可以分为两步:

◉ 按照二叉搜索树的方式插入新节点

◉ 整节点的平衡因子

插入节点,会影响部分祖先节点的平衡因子

更新平衡因子

◉ 插入在左子树,平衡因子--

◉ 插入在右子树,平衡因子++

是否继续往上跟新祖先,要看parent所在子树的高度是否变化

1.parent的平衡因子 == 0

说明parent的平衡因子更新前是1 or-1,插入节点插入矮的那边 ​ parent所在子树的高度不变,不需要继续往上更新

2.parent的平衡因子 == 1/-1

说明parent的平衡因子更新前是0,插入节点插入在任意一边 ​ parent所在的子树高度都会变化了,需要继续往上更新

3.parent的平衡因子 == 2/-2

说明parent的平衡因子更新前是1/-1,插入节点插入在高的那边 ​ 进一步加剧了parent所在的子树的不平衡,已经违反违规了,需要旋转处理

 

AVL树的旋转

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

新节点插入较高左子树的右侧---右右:左单旋

旋转原则:

◉ 保持搜索树的规则

◉ 控制平衡,降低高度(右边高,往左边旋)

 

由上图我们可以观察到以下几点:

✸ 我们要旋转的是平衡因子失调的节点,在图中也就是parent节点

✸ 由于二叉树的性质可知 subRL 的值b,大小位于parentsubR之间,因此可以将左边高度升高,右边高度降低,也就是将subRL连接在parent的右边。之后使subR作为新的“根”节点,parent作为新根节点(subR)的左边。

✸ 平衡因子失调的节点可能是根节点,也有可能不是根节点,因此我们需要判断+保存失调节点的父节点

✸ 子树h的高度可能为0,因此subRL有可能为空,此时需要注意空节点的问题

✸ 改变新节点左右指针的指向时,也需要及时更改节点的父母值

✸ 平衡因子只更新parentsubR为0,因为其他的我们没有动

✸ 插入位置是直线,更新位置是个弯

    //右边高-->左单旋
    void RotateL(Node* parent){
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
        Node* pparent = parent->_parent;//检验parent是根还是子树
        
        parent->_right = subRL;
        if(subRL)//高度不为0的时候在指向,否则出现空指针指向问题
            subRL->_parent = parent;
        
        subR->_left = parent;
        parent->_parent = subR;
        
        if(pparent == nullptr){
            //说明parent原先是根
            _root = subR;
            subR->_parent = nullptr;
        }else{
            //说明parent原先是子树
            if(pparent->_left == parent)
                pparent->_left = subR;
            if(pparent->_right == pparent)
                pparent->_right = subR;
            
            subR->_parent = pparent;
        }
        //更新平衡因子
        parent->_bf = subR->_bf = 0;
        
    }

新节点插入较高左子树的左侧---左左:右单旋

 

  //右旋
    void  RotateR(Node* parent){
        Node* subL = parent->_left;
        Node* subLR = subL->_right;
        Node* pparent = parent->_parent;
        
        parent->_left = subLR;
        if(subLR)
            subLR->_parent = parent;
        
        subL->_right = parent;
        parent->_parent = subL;
        
        if(pparent == nullptr){
            _root = subL;
            subL->_parent = nullptr;
        }else{
            if(pparent->_left == parent)
                pparent->_left = subL;
            if(pparent->_right == parent)
                pparent->_right = subL;
            
            subL->_parent = pparent;
        }
        subL->_bf = parent->_bf = 0;
        
    }

新节点插入较高右子树的左侧---右左:先右单旋再左单旋

由于新节点插入在右子树的左侧:

◉ 对于节点90来说,左子树升高,需要右旋

◉ 对于节点30来说,右子树升高,需要左旋

插入后平衡因子需要改变,此时分为两种情况,h = 0 与 h > 0,其中 h>0 又包括插入左子树还是右子树 

 

由上图我们可以观察到以下几点:

✸ 无论是怎么旋转,我们的目标都是:降高度,拉平衡,关键在于找到subRL

✸ 无论插入在subRL的左子树还是右子树,都不扰乱旋转的次序

✸ 插入的时候要维护新插节点在右子树左端的左节点还是右节点,更新平衡因子

✸ 如果我们不看过程,只看结果会发现就是使subRL作为新根节点,subRL的左右孩子分别送给左子树的右孩子,右子树的左孩子。

    void RotateRL(Node* parent){
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
        
        int bf = subRL->_bf;//维护新插节点在右子树左端的左节点还是右节点
        
        //先右旋
        RotateR(parent->_right);
        //在左旋
        RotateL(parent);
        
        if(bf == 0){
            // subRL就是新插节点,无孩子
            subR->_bf = 0;
            subRL->_bf = 0;
            parent->_bf = 0;
        }else if(bf == -1){
            // 新节点插入在subRL的左端
            subR->_bf = 1;
            subRL->_bf = 0;
            parent->_bf = 0;
        }else if (bf == 1){
            // 新节点插入在subRL的右端
            subR->_bf = 0;
            subRL->_bf = 0;
            parent->_bf = -1;
        }
        else{
            assert(false);
        }
    }

新节点插入较高左子树的右侧--左右:先左单旋再右单旋

    void RotateLR(Node* parent){
        Node* subL = parent->_left;
        Node* subLR = subL->_right;
        int _bf = subLR->_bf;
        
        RotateR(parent->_left);
        RotateL(parent);
        
        if(_bf == 0){
            subL->_bf = 0;
            subLR->_bf = 0;
            parent = 0;
        }else if (_bf == -1){
            subL->_bf = 0;
            subLR->_bf = 0;
            parent->_bf = 1;
        }else if (_bf ==1){
            subL->_bf = -1;
            subLR->_bf = 0;
            parent = 0;
        }else{
            assert(false);
        }
    }

 

AVL树的验证

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

◉ 验证其为二叉搜索树

如果中序遍历可得到一个有序的序列,就说明为二叉搜索树

◉ 验证其为平衡树

1.每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)

2.节点的平衡因子是否计算正确

void TestAVLTree1()
{
    //int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13};
    int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
    AVLTree<int, int> t1;
    for (auto e : a)
    {
        /*if (e == 4)
        {
            int i = 0;
        }*/

        // 1、先看是插入谁导致出现的问题
        // 2、打条件断点,画出插入前的树
        // 3、单步跟踪,对比图一一分析细节原因
        t1.Insert({e,e});
        if(e == 6){
            int a = 1;
        }

        cout <<"Insert:"<< e<<"->"<< t1.IsBalance() << endl;
    }

    t1.InOrder();

    cout << t1.IsBalance() << endl;
}

AVL树的删除(了解)

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

具体实现学生们可参考《算法导论》或《数据结构-用面向对象方法与C++描述》殷人昆版。

AVL树的性能

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

项目文件

//
//  AVLTree.h
//  AVL树
//
//  Created by 南毅 on 2024/7/19.
//
#include <iostream>
using namespace std;
#include <assert.h>
#include <vector>
#include <time.h>

template<class K, class V>
struct AVLTreeNode{
    pair<K,V> _kv;
    AVLTreeNode<K, V>* _left;
    AVLTreeNode<K, V>* _right;
    AVLTreeNode<K, V>* _parent;
    int _bf;//balance factor
    
    AVLTreeNode(const pair<K, V>& kv)
    :_kv(kv),
    _left(nullptr),
    _right(nullptr),
    _parent(nullptr),
    _bf(0)
    {
        
    }
    
};

template <class K,class V>
class AVLTree{
private:
    typedef AVLTreeNode<K, V> Node;
    Node* _root = nullptr;
    
    Node* Copy(Node* root){
        //前序遍历拷贝
        if(root == nullptr)
            return nullptr;
        
        Node* newRoot = new Node(root->_kv);
        Copy(root->_left);
        Copy(root->_right);
        return newRoot;
    }
    
    void Destory(Node* root){
        if(root == nullptr)
            return;
        
        //后序遍历析构
        Destory(root->_left);
        Destory(root->_right);
        delete root;
    }
    
    void _InOrder(Node* root){
        if(root == nullptr)
            return;
        
        _InOrder(root->_left);
        cout << root->_kv.first << " : " << root->_kv.second <<" ";
        _InOrder(root->_right);
    }
    
    int _Size(Node* root)
        {
            return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
        }

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

            return max(_Height(root->_left), _Height(root->_right)) + 1;
        }

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

            int leftHeight = _Height(root->_left);
            int rightHeight = _Height(root->_right);
            // 不平衡
            if (abs(leftHeight - rightHeight) >= 2)
            {
                cout << root->_kv.first << endl;
                return false;
            }
            
            // 顺便检查一下平衡因子是否正确
            if (rightHeight - leftHeight != root->_bf)
            {
                cout << root->_kv.first << endl;
                return false;
            }

            return _IsBalance(root->_left)
                && _IsBalance(root->_right);
        }
public:
    //构造函数
    AVLTree() = default;
    //拷贝函数
    AVLTree(const AVLTree<K,V> &t){
        _root = Copy(t._root);
    }
    //赋值运算符重载
    AVLTreeNode<K, V>& operator=(const AVLTree<K,V> &t){
        swap(_root,t._root);
        return *this;
    }
    
    //析构函数
    ~AVLTree(){
        Destory(_root);
        _root = nullptr;
    }
    
    //中序遍历
    void InOrder(){
        _InOrder(_root);
        cout << endl;
    }
    
    //查找元素
    //只需要传k就能找到v,所以传k就行了
    Node* Find(const K& key){
        Node* cur = _root;
        while(cur){
            if(cur->_kv.first < key){
                cur = cur -> _right;
            }else if (cur->_kv.first > key)
            {
                cur = cur->_left;
            }
            else
            {
                return cur;
            }
        }
        return 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(cur->_kv.first < kv.first){
                parent = cur;
                cur = cur -> _right;
            }else if (cur->_kv.first > kv.first){
                parent = cur;
                cur = cur -> _left;
            }else{
                //找到相同值
                return false;
            }
        }
        
        //找到了待插入位置
        cur = new Node(kv);
        if(kv.first < parent->_kv.first)
            parent->_left = cur;
        else
            parent->_right = cur;
        
        cur->_parent = parent;
        
        
        //更新平衡因子
        while(parent){
            if(parent->_left == cur)
                //插入在左子树 --
                parent->_bf--;
            else
                //插入在右子树 ++
                parent->_bf++;
            
            if(parent->_bf == 0){
                //1️⃣ 插入后parent的平衡因子 == 0
                /*说明parent的平衡因子更新前是1 or-1,插入节点插入矮的那边
                ​parent所在子树的高度不变,不需要继续往上更新*/
                break;
            }else if (parent->_bf == 1 || parent->_bf == -1){
                //插入后parent的平衡因子 == 1/-1
                /*说明parent的平衡因子更新前是0,插入节点插入在任意一边
                ​parent所在的子树高度都会变化了,需要继续往上更新*/
                cur = parent;
                parent = parent->_parent;
            }
            else if (parent->_bf == 2 || parent->_bf == -2){
                // 不平衡了,旋转处理
                if (parent->_bf == 2 && cur->_bf == 1)
                {
                    RotateL(parent);
                }
                else if (parent->_bf == -2 && cur->_bf == -1)
                {
                    RotateR(parent);
                }
                else if (parent->_bf == 2 && cur->_bf == -1)
                {
                    RotateRL(parent);
                }
                else
                {
                    RotateLR(parent);
                }
                break;
            }else{
                // 理论而言不可能出现这个情况
                assert(false);
            }
            
        }
        return true;
    }
    
    //右边高-->左单旋
    void RotateL(Node* parent){
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
        Node* pparent = parent->_parent;//检验parent是根还是子树
        
        parent->_right = subRL;
        if(subRL)//高度不为0的时候在指向,否则出现空指针指向问题
            subRL->_parent = parent;
        
        subR->_left = parent;
        parent->_parent = subR;
        
        if(pparent == nullptr){
            //说明parent原先是根
            _root = subR;
            subR->_parent = nullptr;
        }else{
            //说明parent原先是子树
            if(pparent->_left == parent)
                pparent->_left = subR;
            if(pparent->_right == pparent)
                pparent->_right = subR;
            
            subR->_parent = pparent;
        }
        //更新平衡因子
        parent->_bf = subR->_bf = 0;
        
    }
    //右旋
    void RotateR(Node* parent){
        Node* subL = parent->_left;
        Node* subLR = subL->_right;
        Node* pparent = parent->_parent;
        
        parent->_left = subLR;
        if(subLR)
            subLR->_parent = parent;
        
        subL->_right = parent;
        parent->_parent = subL;
        
        if(pparent == nullptr){
            _root = subL;
            subL->_parent = nullptr;
        }else{
            if(pparent->_left == parent)
                pparent->_left = subL;
            if(pparent->_right == parent)
                pparent->_right = subL;
            
            subL->_parent = pparent;
        }
        subL->_bf = parent->_bf = 0;
        
    }
    //
    void RotateRL(Node* parent){
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
        
        int bf = subRL->_bf;//维护新插节点在右子树左端的左节点还是右节点
        
        //先右旋
        RotateR(parent->_right);
        //在左旋
        RotateL(parent);
        
        if(bf == 0){
            // subRL就是新插节点,无孩子
            subR->_bf = 0;
            subRL->_bf = 0;
            parent->_bf = 0;
        }else if(bf == -1){
            // 新节点插入在subRL的左端
            subR->_bf = 1;
            subRL->_bf = 0;
            parent->_bf = 0;
        }else if (bf == 1){
            // 新节点插入在subRL的右端
            subR->_bf = 0;
            subRL->_bf = 0;
            parent->_bf = -1;
        }
        else{
            assert(false);
        }
    }
    void RotateLR(Node* parent){
        Node* subL = parent->_left;
        Node* subLR = subL->_right;
        int _bf = subLR->_bf;
        
        RotateL(parent->_left);
        RotateR(parent);
        
        if(_bf == 0){
            subL->_bf = 0;
            subLR->_bf = 0;
            parent = 0;
        }else if (_bf == -1){
            subL->_bf = 0;
            subLR->_bf = 0;
            parent->_bf = 1;
        }else if (_bf ==1){
            subL->_bf = -1;
            subLR->_bf = 0;
            parent = 0;
        }else{
            assert(false);
        }
    }
    
    bool IsBalance()
    {
        return _IsBalance(_root);
    }

    int Height()
    {
        return _Height(_root);
    }

    int Size()
    {
        return _Size(_root);
    }
    
};

void TestAVLTree1()
{
    //int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13};
    int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
    AVLTree<int, int> t1;
    for (auto e : a)
    {
        /*if (e == 4)
        {
            int i = 0;
        }*/

        // 1、先看是插入谁导致出现的问题
        // 2、打条件断点,画出插入前的树
        // 3、单步跟踪,对比图一一分析细节原因
        t1.Insert({e,e});
        if(e == 6){
            int a = 1;
        }

        cout <<"Insert:"<< e<<"->"<< t1.IsBalance() << endl;
    }

    t1.InOrder();

    cout << t1.IsBalance() << endl;
}

void TestAVLTree2()
{
    const int N = 1000;
    vector<int> v;
    v.reserve(N);
    srand(time(0));

    for (size_t i = 0; i < N; i++)
    {
        v.push_back(rand()+i);
        //cout << v.back() << endl;
    }

    size_t begin2 = clock();
    AVLTree<int, int> t;
    for (auto e : v)
    {
        t.Insert(make_pair(e, e));
        //cout << "Insert:" << e << "->" << t.IsBalance() << endl;
    }
    size_t end2 = clock();

    cout << "Insert:" << end2 - begin2 << endl;
    //cout << t.IsBalance() << endl;

    cout << "Height:" << t.Height() << endl;
    cout << "Size:" << t.Size() << endl;

    size_t begin1 = clock();
    // 确定在的值
    for (auto e : v)
    {
        t.Find(e);
    }

    // 随机值
    /*for (size_t i = 0; i < N; i++)
    {
        t.Find((rand() + i));
    }*/

    size_t end1 = clock();

    cout << "Find:" << end1 - begin1 << endl;
}

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值