sgi-stl3.0 - 手搓平衡二叉搜索树(AVL)

文章目录

前言

一、什么是平衡二叉搜索树

二、结构

三、实现

拷贝构造函数

赋值运算符重载函数

析构函数

插入

左单旋

右单旋

右左双旋

左右双旋

插入部分的代码实现:

中序遍历

查找

高度

Size

四、测试平衡

总结


前言

路漫漫其修远兮,武将上下而求索;


一、什么是平衡二叉搜索树

搜索二叉树虽然可以满足搜索的需求,但是在极端情况下会退化成“链表”的形式而导致其搜索的效率极其低下(搜索效率从O(logN)退化为 O(N)),而其中一种解决这种情况的方法就是AVL树(平衡二叉搜索树)

AVL树是一棵自平衡二叉搜索树,它满足以下性质:

  1. 可以为空树

  2. 若非空,则必须同时满足:

    • 是一棵二叉搜索树(满足BST定义,Binary Search Tree(二叉搜索树));

    • 左子树和右子树都是AVL树

    • 左右子树的高度差(平衡因子)的绝对值不超过 1

这一性质通过对每个结点维护高度平衡来保证,使树在插入和删除操作后依然保持 O(log n) 的查找效率。

其中,为了控制平衡,在AVL树当中引入了一个平衡因子(balance factor)的概念,每一个节点均有一个平衡因子,任何节点的平衡因子等于右子树的高度-左子树的高度(也可以是左子树高度-右子树高度),也就是说任何节点的平衡因子等于0/1/-1, AVL树并不是必须要平衡因子,但是有了平衡因子就可以更方便我们去观察和控制此树是否平衡;

注:因为插入节点的个数是不确定的,势必会存在左右子树高度差为1的情况,我们只需要维护其高度差不超过1就行;

二、结构

AVL树节点结构,此处实现key-value ,在二叉搜索树结构的基础上增加一个指向父节点的指针平衡因子

    //定义节点 --> 在二叉搜索树的基础上增加平衡因子、指向父节点的指针,此处实现存储kv 
    template<class K, class V>
    struct AVLTreeNode
    {
        std::pair<K,V> _kv;
        AVLTreeNode<K,V>* _left;
        AVLTreeNode<K,V>* _right;
        AVLTreeNode<K,V>* _parent;
        int _bf;//平衡因子 = 右子树高度- 左子树高度(此处实现中规定)

        //构造函数
        AVLTreeNode(const std::pair<K,V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
    };

AVL树结构:

//定义AVL树形结构
    template<class K,class V>
    class AVLTree
    {
        typedef AVLTreeNode<K,V> Node;
    public:
        AVLTree():_root(nullptr){}
        //拷贝构造函数
        AVLTree(Node* root){}
        //赋值运算符重载
        AVLTree<K,V>& operator=(const AVLTree<K,V>& t){}
        //析构函数
        ~AVLTree(){}

        //插入
        bool Insert(const std::pair<K,V>& kv){}
        //右单旋 
        void RotateR(Node* parent){}
        //左单旋 
        void RotateL(Node* parent){}
        //右左双旋 
        void RotateRL(Node* parent){}
        //左右双旋  
        void RotateLR(Node* parent){}
        //中序遍历
        void Inorder(){}
        //查找 --> 搜索树的效率--》O(logn)
        Node* Find(const K& key){}
        //高度和size
        int Height(){}
        int Size(){}
    private:
        Node* _root = nullptr;
    };

三、实现

拷贝构造函数

需要遍历所有节点(前序遍历),创建节点并处理其链接关系

参考代码如下:

        //拷贝构造函数
        AVLTree(const AVLTree<K,V>& other)
        {   
            _root = copy(other._root , nullptr);
        }   
        Node* copy(Node* root , Node * parent)
        {
            //前序遍历
            if(root==nullptr) return nullptr;
            Node* newnode = new Node(root->_kv);
            newnode->_parent = parent;
            newnode->_bf = root->_bf;
            newnode->_left = copy(root->_left ,newnode);
            newnode->_right = copy(root->_right, newnode);

            return newnode;
        }

赋值运算符重载函数

参考代码如下:

        //赋值运算符重载
        AVLTree<K,V>& operator=(AVLTree<K,V> other)//现代写法,利用现成的拷贝构造+swap
        {
            std::swap(_root,other._root);
            return *this;
        }

析构函数

后序遍历释放节点

参考代码如下:

      //析构函数 --》后序遍历递归释放节点
        ~AVLTree()
        {
            Destory(_root);
            _root = nullptr;
        }
        void Destory(Node* root)
        {
            if(root==nullptr) return;
            Destory(root->_left);
            Destory(root->_right);
            delete root;
        }

插入

与二叉搜索树一致,需要找到插入值应该在的合适的位置(插入一个值的过程按照二叉搜索树的规则进行插入),但是AVL树需要控制其平衡(根据平衡因子的情况进行旋转处理)

AVL树的插入操作包含向上更新平衡因子局部旋转调整两个关键步骤,旋转后无需继续向上追溯;

过程:

插入节点后,只会影响其祖先节点的高度,因此只需从插入节点开始,沿着父路径向上更新至根节点的平衡因子。实际中,最坏情况下需更新到根节点,但某些情况可在中途停止(如遇到平衡因子变为0时,子树高度不变,无需继续向上更新)。

  • 若更新路径上所有节点的平衡因子均保持在 {-1, 0, 1},则插入完成。

  • 若遇到某个节点的平衡因子变为 -2 或 2,则该子树不平衡,需要进行旋转调整。

平衡因子的更新:右子树高度-左子树高度(本篇博文当中,规定为右子树高度-左子树高度)

只有子树高度变化才会影响当前节点的平衡因子;

左单旋

 

代码如下:

        //左单旋 
        //       parent                      subR
        //                 subR  --》  parent    
        //            subRL                 subRL
        void RotateL(Node* parent)
        {
            Node* subR = parent->_right;
            Node* subRL = subR->_left;

            parent->_right = subRL;
            subR->_left = parent;
            if(subRL) subRL->_parent = parent;

            Node* parentParent = parent->_parent;
            //判断parent 是不是根节点
            parent->_parent = subR;
            if(parent == _root)
            {
                _root = subR;
                subR->_parent = nullptr;
            } 
            else
            {
                //parent 不是根节点,判断parent 位于其父节点的左边还是右边
                if(parentParent->_left == parent)
                {
                    parentParent->_left = subR;
                }
                else
                {
                    parentParent->_right = subR;
                }
                subR->_parent = parentParent;
            }
            //更新平衡因子
            subR->_bf = parent->_bf = 0;
        }

右单旋

代码如下:

        //右单旋 --》左子树高(bf 为-2)
        void RotateR(Node* parent)
        {
            Node* subL = parent->_left;
            Node* subLR = subL->_right;

            parent->_left = subLR;
            subL->_right = parent;
            //subLR的父亲指向
            if(subLR) subLR->_parent = parent;
            //处理subL与parent 的父亲指向,需要判断parent 是否为根节点
            Node* parentParent = parent->_parent;
            parent->_parent = subL;

            if(parent == _root)
            {
                _root = subL;
                subL->_parent = nullptr;
            }
            else
            {
                //判断subL 在 parentParent 的左边还是右边
                if(parentParent->_left == parent)
                {
                    parentParent->_left = subL;
                }
                else 
                {
                    parentParent->_right = subL;
                }
                subL->_parent = parentParent;
            }
            //处理平衡因子 --》subL 与 parent的平衡因子为0
            subL->_bf = parent->_bf = 0;
        }

右左双旋

当parent 的平衡因子为2 && subR的平衡因子为-1 时,需要进行右左双旋

对于parent、subR、subRL 平衡因子的更新,需要对subRL的平衡因子分情况讨论 :

  • 0  ->  parent、subR、subRL 平衡因子均为0
  • 1  ->  parent 平衡因子为-1、subR、subRL 平衡因子为0
  • -1  -> subR 平衡因子为1, parent、subRL 平衡因子为0

图解如下:

代码如下:

 //右左双旋 --》parent的平衡因子为2 && subR 的平衡因子为-1
        //       parent          右单旋          parent                  左单旋         subRL
        //                subR  -->                     subRL            -->      parent    subR
        //            subRL                                     subR
        void RotateRL(Node* parent)
        {
            Node* subR = parent->_right;
            Node* subRL = subR->_left;

            int bf = subRL->_bf;//根据 subRL原来的平衡因子来更新parent subR subRL的平衡因子
            //以subR为旋转点进行右单旋
            RotateR(subR);
            RotateL(parent);
            //更新平衡因子
            if(bf==0)
            {
                parent->_bf = subR->_bf = subRL->_bf = 0;
            }
            else if(bf == -1)//左边高
            {
                parent->_bf = 0;
                subRL->_bf = 0;
                subR->_bf = 1;
            }
            else if(bf == 1)
            {
                parent->_bf = -1;
                subR ->_bf = 0;
                subRL->_bf = 0;
            }
            else 
            {
                assert(false);
            }
        }

左右双旋

当parent 的平衡因子为-2 && subR的平衡因子为1 时,需要进行左右双旋

对于parent、subL、subLR 平衡因子的更新,需要对subRL的平衡因子分情况讨论 :

  • 0  ->  parent、subL、subLR 平衡因子均为0
  • 1  ->  subL平衡因子为-1、parent、subLR 平衡因子为0
  • -1  -> parent平衡因子为1,subL、subLR 平衡因子为0

代码如下:

//左右双旋  ->parent的平衡因子为-2 && subL的平衡因子为1
        //        parent       左单旋          parent     右单旋           subLR
        //   subL              --》     subLR             -->         subL       parent
        //        subLR            subL        
        void RotateLR(Node* parent)
        {
            Node* subL = parent->_left;
            Node* subLR = subL->_right;

            int bf = subLR->_bf;
            //先以subL为旋转点进行左单旋,再以parent 为旋转点进行右单旋
            RotateL(subL);
            RotateR(parent);
            if(bf==0)
            {
                parent->_bf = 0;
                subL->_bf = 0;
                subLR->_bf = 0;
            }
            else if(bf==1)
            {
                parent->_bf = 0;
                subL->_bf = -1;
                subLR->_bf = 0;
            }
            else if(bf ==-1)
            {
                parent->_bf = 1;
                subL->_bf = 0;
                subLR->_bf = 0;
            }
            else 
            {
                assert(false);
            }
        }

插入部分的代码实现:

        //插入
        bool Insert(const std::pair<K,V>& kv)
        {
            //找到合适的位置进行插入,向上更新平衡因子,然后根据平衡因子的值进行调整(当前节点的平衡因子超过2需要进行旋转处理)
            if(_root == nullptr)
            {
                _root = new Node(kv);
                return true;
            }
            Node* cur = _root,*prev = nullptr;
            while(cur)
            {
                if(kv.first < cur->_kv.first)
                {
                    prev = cur;
                    cur = cur->_left;
                }
                else if(kv.first > cur->_kv.first)
                {
                    prev = cur;
                    cur = cur->_right;
                }
                else 
                {
                    //找到了相等的,此处实现不支持冗余版本
                    return false;
                }
            }
            //找到了合适的位置 - 插入在prev 的左子树,平衡因子--,插入在prev 的右子树,平衡因子++
            cur = new Node(kv);
            if(kv.first < prev->_kv.first)
            {
                prev->_left = cur;
            }
            else 
            {
                prev->_right = cur;
            }
            cur->_parent = prev;
            //更新平衡因子
            while(prev)//当prev 为nullptr , 意味着cur 为根节点 --》平衡因子更新到根节点就不用再更新了
            {
                if(cur == prev->_left)
                {
                    prev->_bf--;
                }
                else 
                {
                    prev->_bf++;
                }
                //根据平衡因子的值来判断需要做什么对应的处理动作
                if(prev->_bf == 0)//插入的节点位于矮的那边,树的高度没有变化;不平衡-》平衡
                {
                    break;
                }
                else if(prev->_bf ==1 || prev->_bf ==-1)//平衡-》不平衡,可能会影响祖先节点
                {
                    cur = prev;
                    prev = prev->_parent;
                }
                else if(prev->_bf == 2 || prev->_bf ==-2)//不平衡-》更不平衡;需要进行旋转处理
                {
                    //进行旋转处理 --> 旋转之后就是平衡的,无需再向上更新平衡因子
                    if(prev->_bf == -2 && cur->_bf == -1)//右单旋
                    {
                        RotateR(prev);
                    }
                    else if(prev->_bf == 2 && cur->_bf == 1)//左单旋
                    {
                        RotateL(prev);
                    }
                    else if(prev->_bf == -2 && cur->_bf==1)//左右双旋
                    {
                        RotateLR(prev);
                    }
                    else if(prev->_bf ==2 && cur->_bf == -1)//右左双旋
                    {
                        RotateRL(prev);
                    }
                    else 
                    {
                        assert(false);
                    }
                    break;
                }
                else 
                {
                    assert(false);
                }
            }
            return true;
        }

中序遍历

        //中序遍历
        void Inorder()
        {
            _Inorder(_root);
            std::cout << std::endl;
        }
        void _Inorder(Node* root)
        {
            if(root==nullptr) return;
            _Inorder(root->_left);
            std::cout << root->_kv.first << " ";
            _Inorder(root->_right);
        }

查找

        //查找 --> 搜索树的效率--》O(logn)
        Node* Find(const K& key)
        {
            Node* cur = _root;
            while(cur)
            {
                if(key < cur->_kv.first)
                {
                    cur = cur->_left;
                }
                else if(key > cur->_kv.first)
                {
                    cur = cur->_right;
                }
                else 
                {
                    return cur;
                }
            }
            return nullptr;
        }

高度

        //高度
        int Height()
        {
            return _Height(_root);
        }
        int _Height(Node* root)
        {
            if(root==nullptr) return 0;
            int left = _Height(root->_left);
            int right = _Height(root->_right);
            return std::max(left,right) +1;
        }
     

Size

        int Size()
        {
            //遍历求个数
            return _Size(_root);
        }
        int _Size(Node* root)
        {
            if(root==nullptr) return 0;
            return _Size(root->_left) + _Size(root->_right) + 1;
        }

四、测试平衡

平衡的测试思想:递归计算当前根节点的平衡因子,与当前所记录的 _bf 进行比较,当这棵树当中的所有平衡因子与我们递归计算的结果相同,就说明我们所写的AVL代码逻辑没有问题;

测试功能函数:

        //对AVL树的平衡进行检测 --》遍历平衡因子,按照属性结构的特点计算与当前节点的平衡因子进行比较
        bool IsBanlanceTree()
        {
            return _IsBalanceTree(_root);
        }
        bool _IsBalanceTree(Node* root)
        {
            //空树也是AVL树
            if(root==nullptr) return true;

            int leftHeight = _Height(root->_left);
            int rightHeight = _Height(root->_right);
            int diff = rightHeight - leftHeight;//此处规定平衡因子 = 右子树高度-左子树高度
            //如果平衡因子与我们计算出来的diff 不相等,或者diff 的绝对值大于1,就说明该树不为平衡二叉搜索树
            if(abs(diff)>1)
            {
                std::cout << "当前树不为平衡二叉搜索树" << std::endl;
                return false;
            } 
            if(diff != root->_bf)
            {
                std::cout << root->_kv.first << "平衡因子异常" << std::endl;
                return false;
            }
            //走到此处如果其子树均为平衡二叉树,说明该树为平衡二叉搜索树
            return _IsBalanceTree(root->_left)&&_IsBalanceTree(root->_right);
        }

测试代码:

#include"AVLTree.hpp"

void TestAVLTree1()
{
    zjx::AVLTree<int,int> t;
    // int a[] = {16,3, 7,11, 9,26,18,14,15};
    int a[] = {4,2,6,1,3,5,15,7,16,14};
    for(auto e: a)
    {
        t.Insert({e,e});
    }
    t.Inorder();

    std::cout << t.IsBanlanceTree() << std::endl;
    std::cout <<"高度: " << t.Height() << std::endl;
    std::cout << "size: " << t.Size() << std::endl; 

    zjx::AVLTree<int,int> ct;
    ct = t;
    ct.Inorder();
}

int main()
{
    TestAVLTree1();
    return 0;
}

测试结果:


总结

AVL树是一种自平衡二叉搜索树,通过平衡因子(右子树高度-左子树高度)确保树的高度差不超过1,从而维持O(logn)的查找效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值