avl树 订阅
在计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis,他们在1962年的论文《An algorithm for the organization of information》中发表了它。 展开全文
在计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis,他们在1962年的论文《An algorithm for the organization of information》中发表了它。
信息
外文名
AVL tree
特    点
最先发明
发明时间
1962
含    义
自平衡二叉查找树
中文名
AVL树
词汇来源
计算机科学
AVL树特点
AVL树本质上还是一棵二叉搜索树,它的特点是: [1]  1.本身首先是一棵二叉搜索树。2.带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)。
收起全文
精华内容
下载资源
问答
  • 数据结构之AVL树详解

    2020-12-26 05:31:33
    AVL树是最早提出的自平衡二叉树,在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树。AVL树得名于它的发明者G.M. Adelson-Velsky和E.M. Landis。AVL树种查找、插入和删除在平均和最坏情况下...
  • AVL树的python实现(自平衡二叉树) 描述: 这是具有以下外部方法的平衡二叉搜索树的实现: insert (data) 将数据插入树中,如果它尚未包含在树中 insertList (list)通过迭代调用insert将list中的数据元素插入到...
  • AVL树及JAVA实现

    2019-04-14 01:03:32
    NULL 博文链接:https://128kj.iteye.com/blog/1735464
  • AVL树

    多人点赞 热门讨论 2021-05-25 14:38:24
    一、AVL树的概念 1、什么是AVL树? 当数据有序或者接近有序时,使用二叉搜索树进行存储时,得到的二叉搜索树是一颗单支树,其搜索的时间复杂度为O(N)。 为了解决上述问题,引入了AVL树的概念: AVL树是一颗...

    目录

    一、AVL树的概念

    1、什么是AVL树?

    2、AVL树的节点定义

    二、AVL树的插入操作

    1、插入节点

    2、调整平衡因子

    3、旋转成为一颗AVL树

    三、AVL树的实现


    一、AVL树的概念

    1、什么是AVL树?

    当数据有序或者接近有序时,使用二叉搜索树进行存储时,得到的二叉搜索树是一颗单支树,其搜索的时间复杂度为O(N)。

    为了解决上述问题,引入了AVL树的概念:

    • AVL树是一颗特殊的二叉搜索树
    • 向AVL树中插入一个节点后,树的所有节点的左右孩子节点的高度差的绝对值小于等于1.
    • 即AVL树是一颗二叉搜索树是,它的左右子子树的高度之差(平衡因子)的绝对值不大于1,并且它的左右子树也是一颗AVL树。

    2、AVL树的节点定义

    template<class T>

    struct TreeNode

    {

            //构造函数

            TreeNode(const T& val)

                    :_val(val),_bf(0),_left(nullptr),_right(nullptr),_parent(nullptr)

            {}

            T _val;

            TreeNode* _left;//左孩子

            TreeNode* _right;//右孩子

            TreeNode* _parent;//双亲节点,不是必须的,为了方便实现而引入的

            int _bf;//平衡因此,不是必须的,为了方便实现引入的。规定,左子树比右子树高平衡因子为-1,一样高为0,右高为1

    };

    二、AVL树的插入操作

    AVL树也是一颗二叉搜索树,因此它在插入数据时也需要先找到要插入的位置然后在将节点插入。不同的是,AVL树插入节点后需要对节点的平衡因子进行调整,如果插入节点后平衡因子的绝对值大于1,则还需要对该树进行旋转,旋转成为一颗高度平衡的二叉搜索树。

    1、插入节点

    该步和二叉搜索树一样,先找到节点在进行插入。需要注意的是,如果是空树直接插入并且让树的根节点等于当前节点。

    //1.找到该节点并插入
    Node* newNode = new Node(val);
    //如果是一颗空树,新插入节点就为跟节点
    if(root == nullptr)
    {
        root = newNode;
        return make_pair(root,true); 
    }
    //找到插入位置
    Node* parent = nullptr;//当前节点的父节点
    Node* cur = root;
    while(cur)
    {
        if((cur->_val).first < (newNode->_val).first)
        {
            //左走
            parent = cur;
            cur = cur->left;
        }
        else if((cur->_val).first > (newNode->_val).first)
        {
            //右走
            parent = cur;
            cur = cur->right;
        }
        else 
        {
            //找到了,不需要插入,直接返回
            return make_pair(cur,false);
        }
    }
    //插入节点                                                                                                                                                                         
    if((parent->_val).first > (newNode->_val).first)
    {
        //插入到parent的左孩子节点处
        parent->left = newNode;
        newNode->_parent = parent;
           
        return make_pair(newNode,true);
    }
    else 
    {
        //插入到parent的右孩子节点处
        parent->right = newNode;
        newNode->_parent = parent;
              
        return make_pair(newNode,true);                                                                                                                                                }

    2、调整平衡因子

    • 插入一个一个节点,只会影响根节点到插入节点的父节点上的平衡因子。

    • 如果插入一个节点后,插入节点的父节点的平衡因子变成了0,则说明插入节点后树的高度没有发生变化,则只影响了父节点的平衡因子。

    • 如果插入一个节点后,该节点的父节点的平衡因子的绝对值大于等于1,在向上更新过程中如果某一个节点的平衡因子变成了0则停止更新,最坏情况下一直要更新到根节点。

    代码实现:

    //2.调节平衡因子
    /*基本思路
     * 从该节点的父节点开始调整,如果父节点的平衡因子变成了0,则停止调整
     * 如果该节点的父节点的平衡因子不是0,则继续向上调整,直到某个节点的
     * 平衡因子变成0或者调整到了根节点                                                                                                                                                 
     * 调整平衡因子的策略是,如果插入的节点是该节点的左孩子节点则平衡因子-1
     * 如果是该节点的右孩子节点,则平衡因子+1
     */  
    cur = newNode;
    while(parent)
    {
        if(cur == parent->left)
        {
            //parent的平衡因子-1
            parent->_bf--;
        }
        else 
        {
            //parent的平衡因子+1
            parent->_bf++;
        }
        //判断parent的平衡因子是否为0
        if(parent->_bf == 0)
        {
            //调整完成
            break;
        }
        else
        {
            //继续调整parent的父节点
            parent = parent->_parent;
        }                                                                                                                                                                       }

    3、旋转成为一颗AVL树

    向AVL树中插入一个节点后,节点的平衡因子可能会发生变化,因此需要对节点的平衡因子进行调整。但是,调整后的节点的平衡因子可能会大于1,也就是说插入一个节点后不在是一颗AVL树。因此,需要通过旋转将调整后的树旋转成一颗AVL树。

    1)右单旋:新插入的节点是较高的左子树的左孩子节点

    思考:如果新插入的节点是2的孩子节点,可以使用右单旋吗?

    注意:要插入的节点必须是较高的左子树(对于-2来说,较高的左子树的根节点是1)的左孩子节点(对于1来说左孩子节点的根节点必须是0),因此当插入到2的时候不能使用右单旋。这时就需要使用左右单旋。

    代码描述:

    //右单旋                                                                                                
    void RotateR(Node* parent)                                                                              
    {                                                                                                       
        Node* parentL = parent->_left;//parent的左孩节点                                                    
        Node* subR = parentL->_right;//parent的左孩子节点的右孩子节点                                       
                                                                                                                
        Node* parentParent = parent->parent;                                                                
        //parent的左孩子节点指向parentL的右孩子节点                                                         
        parentL = subR;                                                                                     
        if(subR)                                                                                            
            subR->_parent = parent;                                                                         
        //parent变成parentL的右孩子节点                                                                     
        subR = parent;                                                                                      
        //parent的父节点变成parent的左孩子节点                                                              
        parent->_parent = parentL;                                                                          
        //parent是根节点                                                                                     
        if(parent == root)                                                                                   
        {                                                                                                    
            root = parentL;                                                                                 
            root->_parent = nullptr;                                                                        
        }                                                                                                    
        else                                                                                                 
        {                                                                                                    
            if(parent == parentParent->_left)                                                               
            {                                                                                               
                parentParent->_left = parentL;                                                              
                parentL->_parent = parentParent;                                                            
            }                                                                                              
            else                                                                                            
            {                                                                                               
                parentParent->_right = parentL;                                                             
                parentL->_parent = parentParent;                                                            
            }                                                                                               
        }
        parent->_bf = parentL->_bf = 0;                                                                                                                                                                                   }    
    

    2)左单旋:新插入节点是较高的右子树的右孩子节点

    注意:如果新插入的节点是较高的右子树的左孩子节点时需要使用右左单旋

    代码描述

    //左单旋
    void RotateL(Node* parent)                                                                                                                                                             
    {
        Node* parentR = parent->_right;//parent的右孩子节点
        Node* subRL = parentR->_left;//parent的右孩子的左孩子节点
    
        Node* parentParent = parent->_parent;
        //parent的右孩子节点指向parent的右孩子节点的左孩子节点
        parentR = subRL;
        if(subRL)
             subRL->_parent = parentR;
        //parent的右孩子节点的左孩子节点指向parent节点
        subRL = parent;
        //parent的父节点指向parent的右孩子节点
        parent->_parent = parentR;
        if(parent == root)
        {
            root = parentR;
            root->_parent = nullptr;
        }
        else
        {
            if(parentParent->_left == parent)
            {
                parentParent->_left = parent;
            }
            else
            {
                parentParent->_right = parent;
            }
            arent->_parent = parentParent;
        }
        parent->_bf = parentR->_bf = 0;
    }
    

    3)左右单旋:新插入节点是较高的左子树的右孩子节点

    注意:左右单旋是指先对该节点的左孩子节点进行左单旋,在对该节点进行右单旋

    代码描述

    //左右单旋    
    void RotateLR(Node* parent)    
    {    
        Node* parentL = parent->_left;    
        Node* subLR = parentL->_right;    
        int subLRBf = subLR->_bf;    
        //左孩子节点进行左单旋    
        RotateL(parent->_left);    
        RotateR(parent);    
        //调整平衡因子    
        if(subLRBf == 1)    
        {    
            parent->_bf = subLR->_bf = 0;    
            parentL->_bf = -1;    
        }    
        else if(subLRBf == -1)    
        {    
            subLR->_bf = parentL->_bf = 0;    
            parent->_bf = -1;    
        }    
        else     
        {    
            parent->_bf = parentL->_bf = subLR->_bf = 0;    
        }    
    }

    4)右左单旋:新插入节点在较高右子树的左孩子节点

    代码描述

    //右左单旋
    void RotateRL(Node* parent)
    {
        Node* parentR = parent->_right; 
        Node* subRL = parentR->_left;                                                                                                                                                      
        int subRLBf = subRL->_bf;
    
        //该节点的右孩子进行右旋转
        RotateR(parent->_right);
        //该节点进行左旋转
        RotateL(parent);
    
        //调整平衡因子
        if(subRLBf == 1)
        {
             parentR->_bf = subRL->_bf = 0;
             parent->_bf = -1;
        }
        else if(subRLBf == -1)
        {
              parentR->_bf = subRL->_bf = 0;
              parent->_bf = 1;
        }
        else 
        {
            parentR->_bf = subRL->_bf = parent->_bf = 0;
        }                                                                                                                                                                              }     

    5)验证

    验证一颗二叉树是否是AVL树时,只要满足以下两个方面就说明该二叉树是AVL树:

    • 该二叉树是一颗二叉搜索树:中序遍历得到有序序列
    • 是否是平衡的:左右子树的平衡因子之差的绝对值小于等于1;插入节点、旋转之后平衡因子是否更新正确、

    代码描述

        //中序遍历AVL树
        static void BSTreeInOrder(Node* node,vector<pair<K,V>>& inOrder)
        {
            //inOrder是输出型参数,将遍历结果保存到该数组中
            if(node == nullptr)
                return;
            BSTreeInOrder(node->_left,inOrder);
            inOrder.push_back(node->_val);                                                                                                                                  
            BSTreeInOrder(node->_right,inOrder);
        }
        bool isBSTree()
        {
            vector<pair<K,V>> inOrder;       BSTreeInOrder(root,inOrder);
            if(inOrder.empty())
                return true;
            //遍历,检查是否有序
            pair<K,V> tmp = inOrder[0];                                                                                                                                     
            for(int i = 1;i < inOrder.size();i++)
            {
                if(tmp.first < inOrder[i].first)
                {
                    tmp = inOrder[i];
                }
                else 
                  return false;
            }
        }
        //二叉树的高度
        static int BSTreeHeight(Node* node)
        {
            if(node == nullptr)
                return 0;
            int left = BSTreeHeight(node->_left);
            int right = BSTreeHeight(node->_right);
            
            return left>right?(left+1):(right+1);
        }
        //判断是否平衡
        static bool _isBalance(Node* root)
        {
            //求左右子树的高度
            int left = BSTreeHeight(root->_left); 
            int right = BSTreeHeight(root->_right);
            if(abs(left-right) <= 1)
            {
                return _isBalance(root->_left) && _isBalance(root->_right);        }
            else 
                return false;
        }
        //验证AVL树
        bool isBalance()
        {
            if(root == nullptr)
                return true;
            if(isBSTree() && _isBalance())
                return true;
            return false;
        }

    三、AVL树的实现

    #pragma once                                                                                                                                                            
        
    #include<iostream>    
    #include<stdlib.h>    
    #include<vector>    
    using namespace std;    
        
    //节点定义    
    template<class K,class V>    
    struct AVLTreeNode    
    {    
        pair<K,V> _val;//节点,键值对    
        int _bf;//平衡因子    
        AVLTreeNode<K,V>* _left;//左子树    
        AVLTreeNode<K,V>* _right;//右子树    
        AVLTreeNode<K,V>* _parent;//双亲节点    
        
        AVLTreeNode(const pair<K,V>& val)    
          :_val(val),_bf(0)    
           ,_left(nullptr),_right(nullptr),_parent(nullptr)    
        {}    
    };    
        
    template<class K,class V>    
    class AVLTree     
    {    
        typedef AVLTreeNode<K,V> Node;    
    private:    
        Node* root = nullptr;//根节点    
    public:    
        //构造函数    
        //析构函数    
        //插入--返回一个键值对,键值对的第一个值是插入节点的指针第二个值是bool值表示是否插入成功    
        pair<Node*,bool> insert(const pair<K,V>& val)    
        {        //1.找到该节点并插入
            Node* newNode = new Node(val);
            //如果是一颗空树,新插入节点就为跟节点
            if(root == nullptr)
            {
                root = newNode;  
                return make_pair(root,true); 
            }
            //找到插入位置
            Node* parent = nullptr;//当前节点的父节点
            Node* cur = root;
            while(cur)
            {
                if((cur->_val).first < (newNode->_val).first)
                {
                    //左走
                    parent = cur;
                    cur = cur->left;
                }
                else if((cur->_val).first > (newNode->_val).first)
                {
                    //右走
                    parent = cur;
                    cur = cur->right;
                }
                else 
                {
                    //找到了,不需要插入,直接返回
                    return make_pair(cur,false);
                }                                                                                                                                                           
            }
            //插入节点
            if((parent->_val).first > (newNode->_val).first)
            {
                //插入到parent的左孩子节点处            parent->right = newNode;  
                newNode->_parent = parent;
            }                                                                                                                                                               
            //2.调整平衡因子
            /*基本思路
             * 从该节点的父节点开始调整,如果父节点的平衡因子变成了0,则停止调整
             * 如果该节点的父节点的平衡因子不是0,则继续向上调整,直到某个节点的
             * 平衡因子变成0或者调整到了根节点
             * 调整平衡因子的策略是,如果插入的节点是该节点的左孩子节点则平衡因子-1
             * 如果是该节点的右孩子节点,则平衡因子+1
             */  
            cur = newNode;
            while(parent)
            {
                if(cur == parent->left)
                {
                    //parent的平衡因子-1
                    parent->_bf--;
                }
                else 
                {
                    //parent的平衡因子+1
                    parent->_bf++;
                }
                //判断parent的平衡因子是否为0
                if(parent->_bf == 0)
                {
                    //调整完成
                    break;
                }
                else if(parent->bf == -1 || parent->bf == 1)
                {                //继续向上调整
                    cur = parent;
                    parent = parent->_parent;
                }
                else 
                {
                    //已经不再是一颗AVL树,需要进行旋转
                    if(parent->_bf == -2)
                    {
                        if(parent->_left->_bf == -1)
                        {
                            //新插入的节点是较高的左子树的左孩子节点---右单旋
                            RotateR(parent); 
                        }
                        else 
                        {
                            //左右单旋
                            RotateLR(parent);
                        }
                    }
                    else if(parent->_bf == 2)
                    {
                        if(parent->_right->_bf == 1)
                        {
                            //左单旋
                            RotateL(parent);
                        }
                        else 
                        {
                            //右左单旋                                                                                                                                      
                            RotateRL(parent);
                        }
                    }
                }
            }       return make_pair(cur,false);
        }
        //右单旋
        void RotateR(Node* parent)
        {
            Node* parentL = parent->_left;//parent的左孩节点
            Node* subR = parentL->_right;//parent的左孩子节点的右孩子节点
            
            Node* parentParent = parent->parent;
            //parent的左孩子节点指向parentL的右孩子节点
            parentL = subR;
            if(subR)
                subR->_parent = parent;
            //parent变成parentL的右孩子节点
            subR = parent;
            //parent的父节点变成parent的左孩子节点
            parent->_parent = parentL;
           //parent是根节点
           if(parent == root)
           {                                                                      
                root = parentL;
                root->_parent = nullptr;
           }
           else 
           {
                if(parent == parentParent->_left)
                {
                    parentParent->_left = parentL;
                    parentL->_parent = parentParent;
                }                                                                                                                                                           
                else 
                {
                    parentParent->_right = parentL;
                    parentL->_parent = parentParent;
                }       }
           parent->_bf = parentL->_bf = 0;
        }
        //左单旋                                                                                                                                                            
        void RotateL(Node* parent)
        {
            Node* parentR = parent->_right;//parent的右孩子节点
            Node* subRL = parentR->_left;//parent的右孩子的左孩子节点
            
            Node* parentParent = parent->_parent;
            //parent的右孩子节点指向parent的右孩子节点的左孩子节点
            parentR = subRL;
            if(subRL)
                subRL->_parent = parentR;
            //parent的右孩子节点的左孩子节点指向parent节点
            subRL = parent;
            //parent的父节点指向parent的右孩子节点
            parent->_parent = parentR;
            if(parent == root)
            {
                root = parentR;
                root->_parent = nullptr;
            }
            else
            {
                if(parentParent->_left == parent)
                {
                    parentParent->_left = parent;
                }
                else 
                {
                    parentParent->_right = parent;
                }            parent->_parent = parentParent;
            }
            parent->_bf = parentR->_bf = 0;
        }
        //左右单旋
        void RotateLR(Node* parent)
        {
            Node* parentL = parent->_left;
            Node* subLR = parentL->_right;
            int subLRBf = subLR->_bf;
            //左孩子节点进行左单旋
            RotateL(parent->_left);
            RotateR(parent);
            //调整平衡因子
            if(subLRBf == 1)
            {
                parent->_bf = subLR->_bf = 0;
                parentL->_bf = -1;
            }
            else if(subLRBf == -1)
            {
                subLR->_bf = parentL->_bf = 0;
                parent->_bf = -1;
            }
            else 
            {
                parent->_bf = parentL->_bf = subLR->_bf = 0;
            }
        }
        //右左单旋                                                                                                                                                          
        void RotateRL(Node* parent)
        {
            Node* parentR = parent->_right; 
            Node* subRL = parentR->_left;
            int subRLBf = subRL->_bf;
            //该节点的右孩子进行右旋转
            RotateR(parent->_right);
            //该节点进行左旋转
            RotateL(parent);
    
            //调整平衡因子
            if(subRLBf == 1)
            {
                parentR->_bf = subRL->_bf = 0;
                parent->_bf = -1;
            }
            else if(subRLBf == -1)
            {
                parentR->_bf = subRL->_bf = 0;
                parent->_bf = 1;
            }
            else 
            {
                parentR->_bf = subRL->_bf = parent->_bf = 0;
            }
        }
        //中序遍历AVL树
        static void BSTreeInOrder(Node* node,vector<pair<K,V>>& inOrder)
        {
            //inOrder是输出型参数,将遍历结果保存到该数组中
            if(node == nullptr)
                return;
            BSTreeInOrder(node->_left,inOrder);
            inOrder.push_back(node->_val);                                                                                                                                  
            BSTreeInOrder(node->_right,inOrder);
        }
        bool isBSTree()
        {
            vector<pair<K,V>> inOrder;       BSTreeInOrder(root,inOrder);
            if(inOrder.empty())
                return true;
            //遍历,检查是否有序
            pair<K,V> tmp = inOrder[0];                                                                                                                                     
            for(int i = 1;i < inOrder.size();i++)
            {
                if(tmp.first < inOrder[i].first)
                {
                    tmp = inOrder[i];
                }
                else 
                  return false;
            }
        }
        //二叉树的高度
        static int BSTreeHeight(Node* node)
        {
            if(node == nullptr)
                return 0;
            int left = BSTreeHeight(node->_left);
            int right = BSTreeHeight(node->_right);
            
            return left>right?(left+1):(right+1);
        }
        //判断是否平衡
        static bool _isBalance(Node* root)
        {
            //求左右子树的高度
            int left = BSTreeHeight(root->_left); 
            int right = BSTreeHeight(root->_right);
            if(abs(left-right) <= 1)
            {
                return _isBalance(root->_left) && _isBalance(root->_right);        }
            else 
                return false;
        }
        //验证AVL树
        bool isBalance()
        {
            if(root == nullptr)
                return true;
            if(isBSTree() && _isBalance())
                return true;
            return false;
        }
    }

     

    展开全文
  • 问题:AVL树目的:了解平衡二叉搜索树的端到端知识,以及如何将其有效地用于解决各种问题。 任务:通过以下操作实现AVL树。 要实施的操作: Operations Complexity 1. Insertion O(log N) 2. Deletion O(log N) 3. ...
  • AVL树的C语言实现

    2016-11-30 13:07:49
    AVL树的C语言实现
  • AVL树的判定问题.rar

    2020-07-01 15:09:25
    AVL 是一种平衡二叉搜索AVL 有一个特点,所有节点的平衡因子不能大于 1,即所有节点的左子树与右子的深度差只能为-1,0,1。根据这个概念,判断 AVL 就是去判断一棵二叉树是否是二叉搜索,并且是否...
  • 实现了红黑树、AVL树的基本功能增删改查。学习交流,共同进步
  • 潜析AVL树AVL树的双旋转 接上篇博文:简析AVL树AVL树的概念及单旋转 AVL树如何恢复平衡之双旋转 首先假设我们有一颗已经处于平衡的AVL树: 上篇博文已经解决了LL和RR两种情况的平衡恢复解决方案—-单旋转。这篇...
  • 该程序通过C++实现了AVL树的一些基础操作:1.编写AVL树判别程序,并判别一个二叉搜索树是否为AVL树;2.实现AVL树的ADT,包括其上的基本操作:结点的加入和删除;3.实现基本操作的动态演示(图形演示);最重要的是,该...
  • 包含AVL树、B树、红黑树、二叉搜索树、并查集、哈夫曼树、字典树的实现
  • 本文实例讲述了C语言数据结构之平衡二叉树(AVL树)实现方法。分享给大家供大家参考,具体如下: AVL树是每个结点的左子树和右子树的高度最多差1的二叉查找树。 要维持这个树,必须在插入和删除的时候都检测是否出现...
  • avl树实现hash表

    2018-12-29 13:57:58
    利用avl树实现hash表,自测通过。下载代码直接编译,即可运行。
  • 键控集 Java 的不可变集合类: KeyedSet - 使用显式键设置类。 基于AVL树。 CList - 经典的 cons-cell 列表,没有位置。 CodedList - CDR 编码列表。 数组 - 索引集合。 所有类目前都与 Java 6 兼容。
  • 生成 AVL 使用随机生成的数据实现了 AVL 的概念。
  • AVL树的介绍

    2015-09-20 16:35:26
    AVL树的简介,方便初学的读者了解AVL树
  • 构建AVL树

    2021-06-02 22:37:57
    一、理解AVL树 目标:通过调整二叉树结构,提高二叉查找的效率。 构建手段:左旋转、右旋转、双旋转。 类似抖葡萄那样,找到合适的节点作为新的根,拎起来抖两下,树结构就得到了调整。 四种失衡情况: 1)LL(left...

    AVL(Adelson-Velskii and Landis)树是**满足平衡条件的二叉搜索树:左右子树的深度差不大于1。**


    一、理解AVL树

    意义:通过调整二叉树结构,提高二叉查找的效率。

    调整手段:左旋转、右旋转、双旋转。

    类似抖葡萄那样,找到合适的节点作为新的根,拎起来抖两下,树结构就得到了调整


    四种失衡情况:

    1)LL(left-left)型:

    失衡位置从名字可以看出,左子树的左侧挂了太多节点;

    此时需要的调整是右旋,顺时针旋转一下,即可恢复平衡。

    image-20210602220245784

    2)RR(right-right)型

    image-20210602220822533

    3)LR(left-right)型:失衡位置在左子树的右侧

    调整操作:①先调整左子树,以K为新的根进行左旋(逆时针旋转)操作;②再处理整棵树,再次以K作为新的根,进行右旋操作。

    image-20210602220851092

    4)RL(right-left)型,和LR型对称。

    image-20210602221839890


    二、代码实现

    1、结构定义

    typedef int elem_t;
    
    typedef struct avl_node {
    	elem_t elem;
    	struct avl_node *left;
    	struct avl_node *right;
    	int height;		// 节点高度
    } avl_t;
    

    2、高度计算

    #define MAX(A,B) ((A)>(B)? (A):(B))
    #define HEIGHT(node) (node == NULL ? -1:node->height)
    
    int update_height(avl_t *T) {
    	return MAX(HEIGHT(T->left), HEIGHT(T->right)) + 1;
    }
    

    3、失衡处理操作(结合图片理解)

    // 左子树不平衡,太高,右旋转
    avl_t *LL(avl_t *T) {
    	avl_t *L = T->left;	// 作为新的根
    	T->left = L->right;	
    	L->right = T;
    
    	T->height = update_height(T);
    	L->height = update_height(L);
    	return L;
    }
    // 右子树不平衡,太高,左旋转
    avl_t *RR(avl_t *T) {
    	avl_t *R = T->right;	// 作为新的根
    	
    	T->right = R->left;	
    	R->left = T;
    
    	T->height = update_height(T);
    	R->height = update_height(R);
    	return R;
    }
    
    avl_t *LR(avl_t *T) {
    	T->left = RR(T->left);
    	return LL(T);
    }
    
    avl_t *RL(avl_t *T) {
    	T->right = LL(T->right);
    	return RR(T);
    }
    

    4、通过插入操作,构建AVL树

    avl_t *avl_new_node(elem_t e, avl_t *left, avl_t *right) {
    	avl_t *node = malloc(sizeof(*node));
    	node->elem = e;
    	node->left = left;
    	node->right = right;
    	node->height = 0;
    	return node;
    }
    
    
    // 在插入节点后要保证树的平衡
    avl_t *insert(avl_t *T, elem_t e) {
    	if (T == NULL) 
    		return avl_new_node(e, NULL, NULL);
    	
    	if (e < T->elem) {	// 往左子树中插入节点
    		T->left = insert(T->left, e);
    		if (HEIGHT(T->left) - HEIGHT(T->right) > 1) {
    			if (e < T->left->elem)
    				T = LL(T);
    			else
    				T = LR(T);
    		}
    	} else if (e > T->elem) {	// 往右子树中插入节点
    		T->right = insert(T->right, e);
    		if (HEIGHT(T->right) - HEIGHT(T->left) > 1) {
    			if (e > T->right->elem)
    				T = RR(T);
    			else
    				T = RL(T);
    		}
    	} 
    
    	T->height = update_height(T);
    	return T;
    }
    
    int main() {
        // 构造AVL树测试
    	elem_t arr[] = {12, 2, 6, 19, 8, 25};
    	int len = sizeof(arr)/sizeof(arr[0]);
    	avl_t *T = NULL;
    	for (int i = 0; i < len; i++) {
    		T = insert(T, arr[i]);
    	}
    	in_order(T);	// 中序遍历,结果为升序数组
    }
    

    5、删除操作就不展示了,例程代码放这里:https://gitee.com/zhoujiabo/leetcode_c/blob/master/AVL.c

    展开全文
  • 最近在看数据结构,用c++实现了下avl树插入、删除、打印实现。实现之后,只达到可以使用的程度,还有较大的重构、优化空间。有感兴趣的同学 可以尝试改进,拿来共享。共同进步
  • AVL树原理详解与实现(附代码)

    万次阅读 2021-08-14 20:04:11
    AVL树的调整3. AVL树的实现4. 完整代码,带测试程序 1. 平衡二叉树的定义 平衡说的是树的高度平衡,平衡二叉树可以这么定义: 一颗空树 如果不是空树,那么它的左子树和右子树都是平衡二叉树,且左右子树的高度差...

    上一篇文章讲了二叉搜索树,但是它会在极端情况下退化为链表,造成查找时间复杂度退化为 O ( N ) O(N) O(N),那么怎样才能让它不退化为链表呢?这篇文章告诉你,快来看看把!

    1. 平衡二叉树的定义

    平衡说的是树的高度平衡,平衡二叉树可以这么定义:

    1. 一颗空树
    2. 如果不是空树,那么它的左子树和右子树都是平衡二叉树,且左右子树的高度差绝对值不超过1

    树的高度怎么定义?

    1. 如果是空树,高度为0
    2. 如果不是空树,那高度就是左子树和右子树的高度最大值+1;

    举个例子:

    在这里插入图片描述
    A的左子树高度3,右子树高度为1,左右子树高度差2,因此这不是平衡二叉树

    又如:
    在这里插入图片描述
    虽然A的左子树和右子树的高度差是0,但是其左右子树都不是平衡二叉树,因此整棵树不平衡。

    2. AVL树的调整

    AVL树是一颗平衡的二叉搜索树,它与普通的二叉搜索树最大的区别就是它的平衡性,平衡性保证了二叉搜索树不会在某些情况下退化为链表,保证了最坏的查找时间复杂度为 O ( N ) O(N) O(N),那么,AVL树如何保证平衡的呢?就是通过调整。那AVL树不平衡的时候,有哪些情况呢?下面一个一个看。

    案例1: 如下图所示的二叉树
    在这里插入图片描述

    根节点1已经失去平衡了(左子树高度1,右子树高度3),现在我们需要做一个操作,使之平衡。这个操作不是只要保证这棵树再次平衡就可以了,还需要保证操作前后,树的中序遍历顺序不改变就是说操作之后,整棵树还是一颗而二叉搜索树。因此我们要找到一个过程,它只调整数的结构,但不影响数的二叉搜索性质。
    那如何操作呢?如下图所示:
    在这里插入图片描述
    这样操作以后,整棵树是平衡的了,那还是二叉搜索树吗?我们只需要看看操作前后的中序遍历结果是不是一个有序列表即可。
    操作之前中序遍历顺序:3,4,5,6,7,8
    操作之后中序遍历顺序:3,4,5,6,7,8
    可见这样的操作是可行的,我们不妨给这个操作起个名字,叫左旋。这样的蜜汁操作是怎么想到的呢?灵感一现吗?不,它是有依据的,如上图所示,根据BST的特性,

    1. 结点6的父节点4,以及4的左子树3,都是小于结点6的,因此可以整体放在结点6的左子树的位置;
    2. 而结点6的左子树5,比6小,但是至少比其父节点4要大,因此可以同时作为结点4的右子树。

    案例2: 如下图所示的情况
    在这里插入图片描述
    根结点8的左子树高度3,右子树高度1,这个情况其实跟上一个情况是对称的,调整这种情况的方式对应叫右旋,如下图所示:
    在这里插入图片描述
    中序遍历结果这里就不验证了哈,原理与左旋类似。

    案例3: 如下图所示的情况
    在这里插入图片描述
    这个形状与案例2中的形状类似,结点9的左子树高度是3,右子树高度1,只不过结点9的左子树的左子树比结点9的左子树的右子树的高度低,如果我们可以把结点9的左子树进行一番操作,改成与案例2中一样的形状就可以按照案例2的情况处理了,因此我们可以先结点9的左子树左旋操作,
    在这里插入图片描述然后再按照案例2的方法右旋即可。

    案例4: 最后一种情况
    在这里插入图片描述
    跟案例3类似,先对结点6右旋,再对结点3左旋即可。
    我们对AVL树进行插入,删除以后,都需要检查树的平衡性,必要时,进行树的调整。

    3. AVL树的实现

    定义AVL树结点类

        class AVLNode<T extends Comparable<T>> {
            T data;
            AVLNode<T> left;
            AVLNode<T> right;
            int height; //树的高度
    
            public AVLNode(T data) {
                this.data = data;
                this.height = 1;//高度初始化为1
            }
        }
    

    定义AVL树实现类

    public class AVLTree<T extends Comparable<T>> {
        /**
         * 树的根节点
         */
        private AVLNode<T> rootNode;
    
        /**
         * AVL树的结点个数
         */
        private int size;
    	
    	//返回树的结点个数
        public int size() {
            return size;
        }
    	// ... 其他方法
    }
    

    首先实现一些utils方法,

    1. 获取树结点的高度:

      /**
       * 获取某个结点的高度
       *
       * @param node 需要获取其高度的结点
       * @return AVL树某个结点的高度
       */
      private int height(AVLNode<T> node) {
          //如果树为空,则返回0,否则返回其height字段的值
          return node == null ? 0 : node.height;
      }
      
    2. 更新树结点的高度

      /**
       * 更新某个AVL树结点的高度
       *
       * @param node 需要更新高度的AVL树结点
       */
      private void updateHeight(AVLNode<T> node) {
          //某个结点的高度是其左子树和右子树的高度的最大值 +1
          if (node != null) node.height = Math.max(height(node.left), height(node.right)) + 1;
      }
      
    3. 获取树某个结点的平衡因子

      /**
       * 获取AVL树某个结点的平衡因子
       * 平衡因子是左子树的高度减去右子树的高度
       *
       * @param node 需要获取其平衡因子的结点
       * @return AVL树某个结点的平衡因子
       */
      private int balanceFactor(AVLNode<T> node) {
          return node == null ? 0 : height(node.left) - height(node.right);
      }
      
    4. 右旋操作

      /**
       * 对AVL树的某个结点进行右旋操作
       * 右旋的逻辑是,node的左指针指向其左子树的右子树,其左子树的右指针指向node,返回node的左子树
       *
       * @param node 需要右旋的结点
       * @return 右旋操作以后的根节点
       */
      private AVLNode<T> rightRotate(AVLNode<T> node) {
          AVLNode<T> left = node.left;
          node.left = left.right;
          left.right = node;
          updateHeight(node);//先更新node的高度,然后才可以更新left的高度
          updateHeight(left);
          return left;
      }
      
    5. 左旋操作

      /**
       * 对AVL树的某个结点进行左旋操作
       * 左旋的逻辑是,node的右指针指向其右子树的左子树,其右子树的左指针指向node,返回node的右子树
       *
       * @param node 需要左旋的结点
       * @return 左旋操作以后的根节点
       */
      private AVLNode<T> leftRotate(AVLNode<T> node) {
          AVLNode<T> right = node.right;
          node.right = right.left;
          right.left = node;
          updateHeight(node);
          updateHeight(right);
          return right;
      }
      
    6. 调整树
      当某棵树的平衡因子:

      • 大于1,则代表需要右旋操作,如果此时其左子树的平衡因子小于0,则代表需要先左旋;
      • 小于-1,则代表需要左旋操作,如果此时其右子树的平衡因子大于0,则代表需要先右旋。

      根据这样的逻辑,调整某个结点的代码如下:

      /**
       * 调整AVL树,主要是根据平衡因子进行旋转操作
       * 因为调整以后,可能改变树的根节点,因此调整方法返回新的根结点
       * 
       * @param root 待调整的根节点
       * @return 调整之后的根节点
       */
      private AVLNode<T> adjustAVLTree(AVLNode<T> root) {
          if (root == null) return null;
      
          //判断是否平衡,如果平衡,则直接返回根节点
          int balanceFactor = balanceFactor(root);
          if (Math.abs(balanceFactor) <= 1) return root;
      
          //否则根据各种不平衡的情况,采取左旋和右旋进行调整
          if (balanceFactor > 1) {//当平衡因子大于1时,需要右旋解决
      
              // 如果根的左子树的平衡因子小于0 则说明根的左子树 右重左轻 需要先对其左旋
              if (balanceFactor(root.left) < 0) root.left = leftRotate(root.left);
              root = rightRotate(root);
          } else {//当平衡因子小于-1时,需要左旋解决
      
              // 如果根的右子树的平衡因子大于0 则说明根的右子树 左重右轻 需要先对其右旋
              if (balanceFactor(root.right) > 0) root.right = rightRotate(root.right);
              root = leftRotate(root);
          }
          return root;
      }
      
    7. 找到某棵树最左的结点

      /**
       * 找到AVL树某个结点上最左的结点
       *
       * @param root 需要搜索到的树根节点
       * @return root上最左的结点
       */
      private AVLNode<T> mostLeft(AVLNode<T> root) {
          if (root == null) return null;
          AVLNode<T> cur = root;
          //只要cur的left不为空,则说明cur还有左子树,cur就不是最左的位置
          for (; cur.left != null; cur = cur.left) ;
          return cur;
      }
      

    put,remove,find方法实现:

    1. put方法
      与二叉搜索树插入类似,只不过这里需要树的调整:
      /**
       * 添加元素到AVL树,不允许null 和重复元素
       *
       * @param value 待添加的元素
       * @return 添加成功--true,否则--false
       */
      public boolean put(T value) {
          if (value == null) return false;
          AVLNode<T> newRoot = put(this.rootNode, value);
          if (newRoot == null) return false;
          this.rootNode = newRoot;
          return true;
      }
      
      /**
       * 添加一个元素到AVL树,因为添加以后可能会改变树的根,所以返回添加后的新的根节点
       *
       * @param root  AVL树根节点
       * @param value 待添加的元素
       * @return 添加后的根结点
       */
      private AVLNode<T> put(AVLNode<T> root, T value) {
          if (root == null) {
              ++this.size;
              return new AVLNode<>(value);
          }
          // 如果找到某个元素和value相等,则不添加,返回
          if (root.data.compareTo(value) == 0) return null;
          if (value.compareTo(root.data) < 0) {//value小于根结点的值,则添加到左子树上,否则添加到右子树上
              AVLNode<T> newLeft = put(root.left, value);
              if (newLeft == null) return null;
              root.left = newLeft;
          } else {
              AVLNode<T> newRight = put(root.right, value);
              if (newRight == null) return null;
              root.right = newRight;
          }
          //更新高度
          updateHeight(root);
      
          //调整AVL树
          return adjustAVLTree(root);
      }
      
    2. find方法
      与二叉搜索树类似
      /**
       * 查找AVL树中是存在结点value
       *
       * @param value 待查找的结点
       * @return 存在--true,不存在--false
       */
      public boolean find(T value) {
          if (value == null || this.rootNode == null) return false;
          AVLNode<T> cur = this.rootNode;
          while (cur != null) {
              if (cur.data.compareTo(value) == 0) return true;
              if (value.compareTo(cur.data) < 0) cur = cur.left;
              else cur = cur.right;
          }
          return false;
      }
      
    3. remove方法
      为了方便设计,定义了一个remove的返回值类型
      /**
       * 删除结果类
       *
       * @param <T>
       */
      private static class RemoveResult<T extends Comparable<T>> {
          boolean success; //当前删除是否成功
          AVLNode<T> retNode; // 删除成功后 返回的新的根结点
      
          public RemoveResult(boolean success, AVLNode<T> retNode) {
              this.success = success;
              this.retNode = retNode;
          }
      
          public RemoveResult(boolean success) {
              this(success, null);
          }
      }
      
      /**
       * 删除元素
       *
       * @param value 待删除的元素
       * @return 删除成功--true,否则--false
       */
      public boolean remove(T value) {
          if (value == null || this.rootNode == null) return false;
          RemoveResult<T> removeResult = remove(this.rootNode, value);
          //删除成功才记录返回的新的根结点
          if (removeResult.success) this.rootNode = removeResult.retNode;
          return removeResult.success;
      }
      
      /**
       * 删除某棵AVL树上某个结点,因为删除可能会更换根结点,因此需要返回删除以后的根结点
       *
       * @param root  树根结点
       * @param value 待删除的值
       * @return 成功返回new RemoveResult<>(true,新的树根节点); 否则 new RemoveResult<>(false,null)
       */
      private RemoveResult<T> remove(AVLNode<T> root, T value) {
          //如果根为空,则直接返回删除失败
          if (root == null) return new RemoveResult<>(false);
      
          //找到带删除的结点,删除逻辑和BST大致一样
          if (root.data.compareTo(value) == 0) {
              if (root.left == null || root.right == null) {
                  --size;
                  if (root.left == null) root = root.right;
                  else root = root.left;
              } else {//当左右子树都不为空时,先从右子树找出最左的结点,作为新的根, 然后将其从右子树删除,
                  AVLNode<T> leftMostOfRight = mostLeft(root.right);
                  leftMostOfRight.right = remove(root.right, leftMostOfRight.data).retNode;
                  leftMostOfRight.left = root.left;
                  root = leftMostOfRight;
              }
              //如果当前结点不是要删除的,则根据大小关系,分别跳到左右子树 进行删除
          } else if (value.compareTo(root.data) < 0) {
              RemoveResult<T> removeLeft = remove(root.left, value);
              if (!removeLeft.success) return removeLeft;
              root.left = removeLeft.retNode;
          } else {
              RemoveResult<T> removeRight = remove(root.right, value);
              if (!removeRight.success) return removeRight;
              root.right = removeRight.retNode;
          }
          //最后需要调整高度和平衡性
          updateHeight(root);
          return new RemoveResult<>(true, adjustAVLTree(root));
      }
      

    4. 完整代码,带测试程序

    package com.victory.common.data_structure;
    
    import java.util.HashSet;
    import java.util.Scanner;
    import java.util.Set;
    
    /**
     * 平衡二叉树 AVL树的实现
     */
    public class AVLTree<T extends Comparable<T>> {
    
        /**
         * 树的根节点
         */
        private AVLNode<T> rootNode;
    
        /**
         * AVL树的结点个数
         */
        private int size;
    
        public int size() {
            return size;
        }
    
        /**
         * 添加元素到AVL树,不允许null 和重复元素
         *
         * @param value 待添加的元素
         * @return 添加成功--true,否则--false
         */
        public boolean put(T value) {
            if (value == null) return false;
            AVLNode<T> newRoot = put(this.rootNode, value);
            if (newRoot == null) return false;
            this.rootNode = newRoot;
            return true;
        }
    
        /**
         * 查找AVL树中是存在结点value
         *
         * @param value 待查找的结点
         * @return 存在--true,不存在--false
         */
        public boolean find(T value) {
            if (value == null || this.rootNode == null) return false;
            AVLNode<T> cur = this.rootNode;
            while (cur != null) {
                if (cur.data.compareTo(value) == 0) return true;
                if (value.compareTo(cur.data) < 0) cur = cur.left;
                else cur = cur.right;
            }
            return false;
        }
    
        /**
         * 删除元素
         *
         * @param value 待删除的元素
         * @return 删除成功--true,否则--false
         */
        public boolean remove(T value) {
            if (value == null || this.rootNode == null) return false;
            RemoveResult<T> removeResult = remove(this.rootNode, value);
            if (removeResult.success) this.rootNode = removeResult.retNode;
            return removeResult.success;
        }
    
        /**
         * 中序打印AVL树
         */
        public void inOrder() {
            inOrder(this.rootNode);
            System.out.println();
        }
    
        private void inOrder(AVLNode<T> root) {
            if (root != null) {
                inOrder(root.left);
                System.out.print(" " + root.data + " ");
                inOrder(root.right);
            }
        }
    
        /**
         * 删除某棵AVL树上某个结点,因为删除可能会更换根结点,因此需要返回删除以后的根结点
         *
         * @param root  树根结点
         * @param value 待删除的值
         * @return 成功返回new RemoveResult<>(true,新的树根节点); 否则 new RemoveResult<>(false,null)
         */
        private RemoveResult<T> remove(AVLNode<T> root, T value) {
            //如果根为空,则直接返回删除失败
            if (root == null) return new RemoveResult<>(false);
    
            //找到带删除的结点,删除逻辑和BST大致一样
            if (root.data.compareTo(value) == 0) {
                if (root.left == null || root.right == null) {
                    --size;
                    if (root.left == null) root = root.right;
                    else root = root.left;
                } else {//当左右子树都不为空时,先从右子树找出最左的结点,作为新的根, 然后将其从右子树删除,
                    AVLNode<T> leftMostOfRight = mostLeft(root.right);
                    leftMostOfRight.right = remove(root.right, leftMostOfRight.data).retNode;
                    leftMostOfRight.left = root.left;
                    root = leftMostOfRight;
                }
                //如果当前结点不是要删除的,则根据大小关系,分别跳到左右子树 进行删除
            } else if (value.compareTo(root.data) < 0) {
                RemoveResult<T> removeLeft = remove(root.left, value);
                if (!removeLeft.success) return removeLeft;
                root.left = removeLeft.retNode;
            } else {
                RemoveResult<T> removeRight = remove(root.right, value);
                if (!removeRight.success) return removeRight;
                root.right = removeRight.retNode;
            }
            //最后需要调整高度和平衡性
            updateHeight(root);
            return new RemoveResult<>(true, adjustAVLTree(root));
        }
    
        /**
         * 找到AVL树某个结点上最左的结点
         *
         * @param root 需要搜索到的树根节点
         * @return root上最左的结点
         */
        private AVLNode<T> mostLeft(AVLNode<T> root) {
            if (root == null) return null;
            AVLNode<T> cur = root;
            for (; cur.left != null; cur = cur.left) ;
            return cur;
        }
    
        /**
         * 添加一个元素到AVL树,因为添加以后可能会改变树的根,所以返回添加后的新的根节点
         *
         * @param root  AVL树根节点
         * @param value 待添加的元素
         * @return 添加后的根结点
         */
        private AVLNode<T> put(AVLNode<T> root, T value) {
            if (root == null) {
                ++this.size;
                return new AVLNode<>(value);
            }
            // 如果找到某个元素和value相等,则不添加,返回
            if (root.data.compareTo(value) == 0) return null;
            if (value.compareTo(root.data) < 0) {//value小于根结点的值,则添加到左子树上,否则添加到右子树上
                AVLNode<T> newLeft = put(root.left, value);
                if (newLeft == null) return null;
                root.left = newLeft;
            } else {
                AVLNode<T> newRight = put(root.right, value);
                if (newRight == null) return null;
                root.right = newRight;
            }
            //更新高度
            updateHeight(root);
    
            //调整AVL树
            return adjustAVLTree(root);
        }
    
        /**
         * 获取某个结点的高度
         *
         * @param node 需要获取其高度的结点
         * @return AVL树某个结点的高度
         */
        private int height(AVLNode<T> node) {
            //如果树为空,则返回0,否则返回其height字段的值
            return node == null ? 0 : node.height;
        }
    
        /**
         * 更新某个AVL树结点的高度
         *
         * @param node 需要更新高度的AVL树结点
         */
        private void updateHeight(AVLNode<T> node) {
            //某个结点的高度是其左子树和右子树的高度的最大值 +1
            if (node != null) node.height = Math.max(height(node.left), height(node.right)) + 1;
        }
    
        /**
         * 获取AVL树某个结点的平衡因子
         * 平衡因子是左子树的高度减去右子树的高度
         *
         * @param node 需要获取其平衡因子的结点
         * @return AVL树某个结点的平衡因子
         */
        private int balanceFactor(AVLNode<T> node) {
            return node == null ? 0 : height(node.left) - height(node.right);
        }
    
        /**
         * 对AVL树的某个结点进行右旋操作
         * 右旋的逻辑是,node的左指针指向其左子树的右子树,其左子树的右指针指向node,返回node的左子树
         *
         * @param node 需要右旋的结点
         * @return 右旋操作以后的根节点
         */
        private AVLNode<T> rightRotate(AVLNode<T> node) {
            AVLNode<T> left = node.left;
            node.left = left.right;
            left.right = node;
            updateHeight(node);//先更新node的高度,然后才可以更新left的高度
            updateHeight(left);
            return left;
        }
    
        /**
         * 对AVL树的某个结点进行左旋操作
         * 左旋的逻辑是,node的右指针指向其右子树的左子树,其右子树的左指针指向node,返回node的右子树
         *
         * @param node 需要左旋的结点
         * @return 左旋操作以后的根节点
         */
        private AVLNode<T> leftRotate(AVLNode<T> node) {
            AVLNode<T> right = node.right;
            node.right = right.left;
            right.left = node;
            updateHeight(node);
            updateHeight(right);
            return right;
        }
    
        /**
         * 调整AVL树,主要是根据平衡因子进行旋转操作
         *
         * @param root 待调整的根节点
         * @return 调整之后的根节点
         */
        private AVLNode<T> adjustAVLTree(AVLNode<T> root) {
            if (root == null) return null;
    
            //判断是否平衡,如果平衡,则直接返回根节点
            int balanceFactor = balanceFactor(root);
            if (Math.abs(balanceFactor) <= 1) return root;
    
            //否则根据各种不平衡的情况,采取左旋和右旋进行调整
            if (balanceFactor > 1) {//当平衡因子大于1时,需要右旋解决
    
                // 如果根的左子树的平衡因子小于0 则说明根的左子树 右重左轻 需要先对其左旋
                if (balanceFactor(root.left) < 0) root.left = leftRotate(root.left);
                root = rightRotate(root);
            } else {//当平衡因子小于-1时,需要左旋解决
    
                // 如果根的左右树的平衡因子大于0 则说明根的右子树 左重右轻 需要先对其右旋
                if (balanceFactor(root.right) > 0) root.right = rightRotate(root.right);
                root = leftRotate(root);
            }
            return root;
        }
    
        private static class AVLNode<T extends Comparable<T>> {
            T data;
            AVLNode<T> left;
            AVLNode<T> right;
            int height;
    
            public AVLNode(T data) {
                this.data = data;
                this.height = 1;
            }
        }
    
        /**
         * 删除结果类
         *
         * @param <T>
         */
        private static class RemoveResult<T extends Comparable<T>> {
            boolean success; //当前删除是否成功
            AVLNode<T> retNode; // 删除成功后 返回的新的根结点
    
            public RemoveResult(boolean success, AVLNode<T> retNode) {
                this.success = success;
                this.retNode = retNode;
            }
    
            public RemoveResult(boolean success) {
                this(success, null);
            }
        }
    
        public static void main(String[] args) {
            automationTest();
        }
    
        /**
         * 手动测试
         */
        private static void artificialTest() {
            AVLTree<Integer> integerAVLTree = new AVLTree<>();
            Scanner sc = new Scanner(System.in);
            String command = null;
            int n;
            while (true) {
                command = sc.next();
                if ("put".equals(command)) {
                    n = sc.nextInt();
                    if (integerAVLTree.put(n)) System.out.println(n + " 插入成功");
                    else System.out.println(n + " 插入失败");
                }
                if ("remove".equals(command)) {
                    n = sc.nextInt();
                    if (integerAVLTree.remove(n)) System.out.println(n + " 删除成功");
                    else System.out.println(n + " 删除失败");
                }
                if ("print".equals(command)) {
                    integerAVLTree.inOrder();
                }
                if ("find".equals(command)) {
                    n = sc.nextInt();
                    if (integerAVLTree.find(n)) System.out.println(n + " 存在当前树中");
                    else System.out.println(n + " 不存在当前树中");
                }
                if ("size".equals(command)) System.out.println("当前树的结点个数是:" + integerAVLTree.size());
                if ("exit".equals(command)) break;
            }
        }
    
        private static volatile boolean runFlag = true;
    
        /**
         * 自动测试
         */
        private static void automationTest() {
    
            new Thread(() -> {
                //验证的逻辑是 设置一个set集合,每次生成一个随机数number,
                // 1. 测试put方法  如果set集合已经包含number的时候,说明AVLTree已经添加过这个数了,再次添加会失败,校验添加是否失败
                //                如果set集合不包含number,则认为AVLTree没有添加过这个数,测试添加是否成功
    
                //2. 测试remove方法 如果set集合已经包含number的时候,说明AVLTree已经添加过这个数了,测试remove是否成功
                //                  如果set集合不包含number,则认为AVLTree没有添加过这个数,测试remove是否失败
    
                //3. 测试find 如果set集合已经包含number的时候,说明AVLTree已经添加过这个数了,测试find是否成功
                //                   如果set集合不包含number,则认为AVLTree没有添加过这个数,测试find是否失败
                Set<Integer> integerSet = new HashSet<>();
                AVLTree<Integer> integerAVLTree = new AVLTree<>();
                int cnt = 0;
                int command, number;
                while (runFlag) {
                    command = (int) (Math.random() * 4) + 1;
                    number = (int) (Math.random() * Integer.MAX_VALUE) + 1;
                    switch (command) {
                        case 1://put
                            if (integerAVLTree.size() < 10000) {
                                boolean put = integerAVLTree.put(number);
                                if (integerSet.contains(number)) {
                                    if (put) {//如果set中已经有了,但还是添加成功了,这是不正确的
                                        System.out.println("hash表中已经包含了:" + number + " 应该添加失败,但是添加成功了");
                                        return;
                                    }
                                } else {
                                    if (!put) {
                                        System.out.println("hash表中没有包含:" + number + " 应该添加成功,但是添加失败了");
                                        return;
                                    }
                                    integerSet.add(number);
                                    ++cnt;
                                }
                            }
                            break;
                        case 2://remove
                            boolean remove = integerAVLTree.remove(number);
                            if (integerSet.contains(number)) {
                                if (!remove) {
                                    System.out.println("hash表中已经包含了:" + number + " 应该删除成功,但是删除失败了");
                                    return;
                                }
                                --cnt;
                                integerSet.remove(number);
                            } else {
                                if (remove) {
                                    System.out.println("hash表中没有包含:" + number + " 应该删除失败,但是删除成功了");
                                    return;
                                }
                            }
                            break;
                        case 3://find
                            if (integerSet.contains(number)) {
                                boolean b = integerAVLTree.find(number);
                                if (!b) {
                                    System.out.println("hash表中已经包含了:" + number + " 应该查找成功,但是查找失败了");
                                    return;
                                }
                            }
                            break;
                        case 4://size
                            if (integerAVLTree.size() != cnt) {
                                System.out.println("size应该是:" + cnt + ",但实际上它是" + integerAVLTree.size());
                                return;
                            }
                            break;
                    }
                }
            }).start();
    
            Scanner scanner = new Scanner(System.in);
            String next = scanner.next();
            if ("exit".equals(next)) runFlag = false;
        }
    }
    
    

    觉得文章不错的话,给个支持吧 ^_^

    展开全文
  • AVL树的查找、删除、插入,并写了测试程序测试程序的正确性
  • 文章目录AVL树实现思路数据结构查找平衡因子旋转右旋左旋右左双旋左右双旋插入删除AVL树的验证中序遍历平衡判断AVL树的性能完整代码实现 AVL树 AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的...


    AVL树

    AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。AVL树得名于它的发明者G.
    M. Adelson-Velsky和E. M. Landis

    AVL树其实就是在二叉搜索树的基础上,引入了平衡因子的概念,通过旋转来调整平衡因子,使得二叉树始终平衡,效率更高。

    特点

    1. 本身首先是一棵二叉搜索树。
    2. 带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。

    二叉搜索树的实现的博客之前写过,所以这里就直接对以前的代码进行改造。
    数据结构 : 二叉搜索树的原理以及实现(C++)


    实现思路

    数据结构

    这里不仅需要左右子树,因为涉及到了大量的平衡因子调节,所以还需要保存父节点的指针,要用到三叉链的结构。

    	struct AVLTreeNode
    	{
    		AVLTreeNode(const std::pair<K, V>& kv)
    			: _left(nullptr)
    			, _right(nullptr)
    			, _parent(nullptr)
    			, _kv(kv)
    			, _bf(0)
    		{}
    
    		AVLTreeNode<K, V>* _left;
    		AVLTreeNode<K, V>* _right;
    		AVLTreeNode<K, V>* _parent;
    		std::pair<K, V> _kv;	//键值对
    		int _bf;	//平衡因子
    	};
    

    查找

    AVL树本质还是二叉搜索树,所以查找部分可以直接复用,不需要修改。

    直接从根节点出发,比根节点大则查找右子树,比根节点小则查找左子树,相同则返回。如果遍历完还没找到,则说明不存在此树中,返回nullptr

    Node* Find(const K& key)
    {
    	//根据二叉搜索树的性质,从根节点出发,比根节点大则查找右子树,比根节点小则查找左子树
    	Node* cur = _root;
    
    	while (cur)
    	{
    		//比根节点大则查找右子树
    		if (key > cur->_kv.first)
    		{
    			cur = cur->_right;
    		}
    		//比根节点小则查找左子树
    		else if (key < cur->_kv.first)
    		{
    			cur = cur->_left;
    		}
    		//相同则返回
    		else
    		{
    			return cur;
    		}
    	}
    
    	//遍历完则说明查找不到,返回false
    	return nullptr;
    }
    

    要讲插入和删除之前,就必须得讲他们两个的核心步骤,旋转和平衡因子的调节。AVL树正是通过这两个步骤来实现其高度平衡的特性。

    平衡因子

    平衡因子,其实就是左右子树的高度差。AVL树通过控制高度差不超过2,来实现平衡。

    通常认为在右边插入节点时,平衡因子+1,左边插入时平衡因子减一。
    例如:
    在这里插入图片描述
    右子树插入一个90,根节点平衡因子+1
    在这里插入图片描述

    当某节点平衡因子为0时,说明他的左右子树平衡
    当平衡因子为1或者-1时,说明左右子树存在高度差,其父节点可能存在不平衡,需要向上继续判断。
    当平衡因子为2或者-2时,说明此时不平衡,需要旋转处理。

    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 (parent->_bf == 2)
    	{
    		//如果右边不平衡,并且子节点也是右边偏重,则左单旋
    		if (cur->_bf == 1)
    		{
    			RotateL(parent);
    		}
    		//如果右边不平衡,而子节点是左边偏重,此时需要先转换为上面的状态,先右单旋再左单旋。但是不能直接右单旋再左单旋,还需要根据情况处理平衡因子
    		else
    		{
    			RotateRL(parent);
    		}
    	}
    	else
    	{
    		//左边不平衡,并且子节点也是左边偏重,右单旋
    		if (cur->_bf == -1)
    		{
    			RotateR(parent);
    		}
    		//同上,左右双旋
    		else
    		{
    			RotateLR(parent);
    		}
    	}
    
    	//旋转完后恢复平衡,更新结束。
    	break;
    }
    

    旋转

    旋转分为四种情景,简单点总结的话就是如果节点呈直线则单旋,折线则双旋,下面一一分析。

    首先讨论直线状态的单旋。
    在这里插入图片描述
    直线状态,也就是某一边不平衡,并且其那一边的子节点也是朝那一边偏重。
    例如这个图,左边不平衡,子节点30也是左边偏重,则单旋。

    右旋

    例如这种情况,当左边不平衡,并且节点呈直线时(左节点的左边偏重),说明需要右旋处理。
    在这里插入图片描述
    此时节点60的平衡因子为-2,说明此时60的位置不平衡,需要旋转,由于是左边偏重,则需要将60向右旋转来恢复平衡。
    在这里插入图片描述
    这就是最简单的右旋。但是通常情况下,这些节点还有各自的子树。
    在这里插入图片描述
    还是按照上面的旋转方法,但是要在保持原有结构的情况下稍微处理。

    对于节点60,如果要将其右旋,就需要让他变成30的右节点,但是30的右节点本来就有数据,所以此时就需要将30的右节点40放到别的位置。那么应该放到哪个位置呢?很简单,因为我们需要将60右旋,所以原本是他左节点的30变成了他的父节点,而他左节点的位置空了出来,所以就可以将40放到60的左边。这时再将60整个变为30的右节点即可。并且因为此时原本的父节点60变为了30的子节点,所以还要处理其与祖父节点的关系。
    在这里插入图片描述
    旋转结束后,调整30和60的平衡因子为0

    总结一下:
    右旋主要分为3个步骤

    1. 让不平衡的结点parent的左子树变为其原本左子树subL的右节点subLR
    2. 让parent变为subL的右子树
    3. 调整新的父节点subL与祖父节点的关系,并调整旋转后的平衡因子
    //右旋
    void RotateR(Node* parent)
    {
    	Node* subL = parent->_left;
    	Node* subLR = subL->_right;
    
    	parent->_left = subLR;
    
    	//如果subLR存在,则让他的父节点指向parent。
    	if (subLR)
    	{
    		subLR->_parent = parent;
    	}
    
    	subL->_right = parent;
    
    	Node* ppNode = parent->_parent;
    	parent->_parent = subL;
    
    	//两种情况
    	//如果parent为根节点,则让subL成为新的根节点
    	if (parent == _root)
    	{
    		_root = subL;
    		subL->_parent = nullptr;
    	}
    	//如果不是根节点,则改变subL与其祖父节点的指向关系
    	else
    	{
    		if (ppNode->_left == parent)
    		{
    			ppNode->_left = subL;
    		}
    		else
    		{
    			ppNode->_right = subL;
    		}
    
    		subL->_parent = ppNode;
    	}
    
    	//左旋完成后平衡,平衡因子归零。
    	subL->_bf = parent->_bf = 0;
    }
    

    左旋

    左旋的思路和右旋一样,只是将顺序翻了过来。
    例如这种情况,当右边不平衡,并且节点呈直线时(右节点的右边偏重),说明需要左旋处理。
    在这里插入图片描述
    在这里插入图片描述
    这就是最简单的左旋。

    下面看看复杂的左旋
    在这里插入图片描述
    还是同样的思路。只是这次把左右调换
    左旋的步骤:

    1. 让不平衡的结点parent的右子树变为其原本右子树subR的右节点subRL
    2. 让parent变为subL的左子树
    3. 调整新的父节点subL与祖父节点的关系,并调整旋转后的平衡因子
      在这里插入图片描述
    //左旋
    void RotateL(Node* parent)
    {
    	Node* subR = parent->_right;
    	Node* subRL = subR->_left;
    
    	parent->_right = subRL;
    
    	if (subRL)
    	{
    		subRL->_parent = parent;
    	}
    
    	subR->_left = parent;
    	Node* ppNode = parent->_parent;
    	parent->_parent = subR;
    
    	if (parent == _root)
    	{
    		_root = subR;
    		subR->_parent = nullptr;
    	}
    	else
    	{
    		if (ppNode->_left == parent)
    		{
    			ppNode->_left = subR;
    		}
    		else
    		{
    			ppNode->_right = subR;
    		}
    
    		subR->_parent = ppNode;
    	}
    
    	subR->_bf = parent->_bf = 0;
    }
    

    下面讨论双旋的情景,双旋发生在折线状态时。
    例如
    在这里插入图片描述
    当某边不平衡,并且对于方向的子树,其反方向偏重时,需要双旋。
    例如上图,左边不平衡,而左子树则是右边偏重。此时呈折线状态。

    右左双旋

    在这里插入图片描述
    继续刚刚那个图,可以看到,他呈折线状态,与前面单旋的状态都不同。所以此时我们可以换个思路,可以先对其进行一次单旋,将其转换成之前的直线状态,再进行一次单旋即可平衡。

    因为其右子树是左边偏重,所以对其右子树先进行一次右旋。
    在这里插入图片描述
    可以看到,此时就恢复成了原本直线的状态,此时因为30节点右边不平衡,所以再进行一次左旋即可完成。
    在这里插入图片描述
    但是这里并不能直接进行一次右单旋然后左单旋,这里有两种情况,如果40插入在了60的左子树,则是上面那种情况,调整完后subR平衡因子为1,parent和subRL为0。

    但是如果将70插入在了60的右子树,则又是另一种情况。
    下面画图
    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    如果40插入在了60的右子树,则调整完后parent平衡因子为-1,subR和subRL为0。
    总结一下:
    右左双旋的步骤如下

    1. 首先因为不平衡那个方向的子树的反方向偏重,呈折现状态,所以需要对其右旋转,让树恢复到直线状态
    2. 直线状态时就和一开始的单旋思路一样,按照单旋处理
    3. 调节平衡因子,根据subRL一开始的平衡因子进行调节,有两种情况,为-1时subR结束后为1,为1时parent结束后为-1。
    //右左双旋
    void RotateRL(Node* parent)
    {
    	Node* subR = parent->_right;
    	Node* subRL = subR->_left;
    	//这里需要保存subRL的平衡因子,来调节旋转完后的平衡因子
    	int bf = subRL->_bf;
    
    	//先右单旋将折线结构转换为直线结构,也就是前面单旋就可以解决的问题。
    	RotateR(subR);
    	//然后再左单旋即可
    	RotateL(parent);
    
    	//根据subRL的bf来调节旋转后的平衡因子
    	if (bf == 1)
    	{
    		parent->_bf = -1;
    		subR->_bf = 0;
    		subRL->_bf = 0;
    	}
    	else if (bf == -1)
    	{
    		parent->_bf = 0;
    		subR->_bf = 1;
    		subRL->_bf = 0;
    	}
    	else
    	{
    		parent->_bf = 0;
    		subR->_bf = 0;
    		subRL->_bf = 0;
    	}
    }
    

    同上思路,就不多说了,直接反过来方向就行。

    左右双旋

    //左右双旋
    void RotateLR(Node* parent)
    {
    	Node* subL = parent->_left;
    	Node* subLR = subL->_right;
    
    	int bf = subLR->_bf;
    
    	RotateL(subL);
    	RotateR(parent);
    
    	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
    	{
    		parent->_bf = 0;
    		subL->_bf = 0;
    		subLR->_bf = 0;
    	}
    }
    

    插入

    插入分为三个步骤

    1. 按照二叉搜索树的规则找到合适的位置插入
    2. 更新插入后的平衡因子
    3. 根据平衡因子来选择是否进行旋转调节。

    思路前面已经讲了,这里直接复用代码就行,具体步骤也写在注释里

    	bool Insert(const std::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
    		{
    			return false;
    		}
    	}
    
    	//插入节点
    	cur = new Node(kv);
    
    	//判断插入位置
    	if (cur->_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 (parent->_bf == 2)
    			{
    				//如果右边不平衡,并且子节点也是右边偏重,则左单旋
    				if (cur->_bf == 1)
    				{
    					RotateL(parent);
    				}
    				//如果右边不平衡,而子节点是左边偏重,此时需要先转换为上面的状态,先右单旋再左单旋。但是不能直接右单旋再左单旋,还需要根据情况处理平衡因子
    				else
    				{
    					RotateRL(parent);
    				}
    			}
    			else
    			{
    				//左边不平衡,并且子节点也是左边偏重,右单旋
    				if (cur->_bf == -1)
    				{
    					RotateR(parent);
    				}
    				//同上,左右双旋
    				else
    				{
    					RotateLR(parent);
    				}
    			}
    
    			//旋转完后恢复平衡,更新结束。
    			break;
    		}
    	}
    
    	return true;
    }
    

    删除

    AVL树的删除极为复杂,数据结构,算法导论这些书里仅仅只是提及了思路而没有实现。我自己实现了发现也大概要100多行,考虑的情况极为复杂。

    虽然代码极为复杂,但是思路还是很简单,分为以下几步。

    1. 按照二叉搜索树的规则删除
    2. 更新平衡因子,并且进行旋转来调整(最坏情况下可能会一直调整到根节点)。

    这里就直接复用上面平衡因子更新的代码以及之前博客实现的二叉搜索树的删除(博客链接在开始处),将其合并处理即可。

    思路之前写过,就不再单独提出来说,直接写在注释里。

    bool erase(const K& key)
    {
    	//删除直接按照二叉搜索树的规则删除,然后再进行平衡因子的更新即可
    	Node* cur = _root;
    	Node* parent = cur;
    	/*
    		删除有三种情况,一种是删除叶子节点,可以直接删除
    		第二种情况,如果删除的节点只有一个子树,那么删除这个节点后,就让父节点指向他的这一子树
    		前两种情况可以合并处理
    
    		第三种情况则是左右子树都不为空,此时选择一个来节点来替换他后,再删除,就可以不破坏原有结构
    		如果要保持原有结构不变化,那么选择的节点必须要和删除节点在中序遍历中是连续的,而满足的只有两个节点,一个是其左子树的最大值,一个是其右子树的最小值。
    	*/
    	//删除部分
    	while (cur)
    	{
    		//找到删除的位置
    		if (key > cur->_kv.first)
    		{
    			parent = cur;
    			cur = cur->_right;
    		}
    		else if (key < cur->_kv.first)
    		{
    			parent = cur;
    			cur = cur->_left;
    		}
    		else
    		{
    			//前两种情况合并处理,如果当前结点只有一个子树,则让父节点指向他的子树
    			//处理只有右子树时					
    			if (cur->_left == nullptr)
    			{
    				//如果当前节点为根节点,则让右子树成为新的根节点
    				if (cur == _root)
    				{
    					_root = cur->_left;
    				}
    				else
    				{
    					//判断当前节点是他父节点的哪一个子树
    					if (parent->_right == cur)
    					{
    						parent->_right = cur->_right;
    					}
    					else
    					{
    						parent->_left = cur->_right;
    					}
    				}
    
    				delete cur;
    			}
    			//处理只有左子树时	
    			else if (cur->_right == nullptr)
    			{
    				//如果当前节点为根节点,则让左子树成为新的根节点
    				if (cur == _root)
    				{
    					_root = cur->_right;
    				}
    				else
    				{
    					if (parent->_right == cur)
    					{
    						parent->_right = cur->_left;
    					}
    					else
    					{
    						parent->_left = cur->_left;
    					}
    				}
    
    				delete cur;
    			}
    			//处理左右子树都不为空时,选取左子树的最右节点或者右子树的最左节点
    			else
    			{
    				//这里我选取的是左子树的最右节点
    
    				Node* LeftMax = cur->_left;
    				Node* LeftMaxParent = cur;
    
    				//找到左子树的最右节点
    				while (LeftMax->_right)
    				{
    					LeftMaxParent = LeftMax;
    					LeftMax = LeftMax->_right;
    				}
    
    				//替换节点
    				std::swap(cur->_kv, LeftMax->_kv);
    
    				//判断当前节点是他父节点的哪一个子树, 因为已经是最右子树了,所以这个节点的右子树为空,但是左子树可能还有数据,所以让父节点指向他的左子树
    				//并且删除最右节点
    				if (LeftMax == LeftMaxParent->_left)
    				{
    					LeftMaxParent->_left = LeftMax->_left;
    				}
    				else
    				{
    					LeftMaxParent->_right = LeftMax->_left;
    				}
    
    				delete LeftMax;
    			}
    
    			//删除成功,中断
    			break;
    		}
    	}
    
    	//查找不到
    	if (cur == nullptr)
    		return false;
    
    	//更新平衡因子
    	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 (parent->_bf == 2)
    			{
    				//如果右边不平衡,并且子节点也是右边偏重,则左单旋
    				if (cur->_bf == 1)
    				{
    					RotateL(parent);
    				}
    				//如果右边不平衡,而子节点是左边偏重,此时需要先转换为上面的状态,先右单旋再左单旋。但是不能直接右单旋再左单旋,还需要根据情况处理平衡因子
    				else
    				{
    					RotateRL(parent);
    				}
    			}
    			else
    			{
    				//左边不平衡,并且子节点也是左边偏重,右单旋
    				if (cur->_bf == -1)
    				{
    					RotateR(parent);
    				}
    				//同上,左右双旋
    				else
    				{
    					RotateLR(parent);
    				}
    			}
    
    			//旋转完后恢复平衡,更新结束。
    			break;
    		}
    	}
    
    	return true;
    }
    

    AVL树的验证

    如要验证AVL树,有两个方法。
    一是判断其是否具有二叉搜索树的特性,可以通过中序遍历来看看是否有序来判断。
    二是通过判断他所有的子树是否两边高度平衡,来判断其是否具有平衡的特性。

    中序遍历

    		void _InOrderTravel(Node* root) const
    		{
    			if (root == nullptr)
    				return;
    
    			_InOrderTravel(root->_left);
    
    			std::cout << root->_kv.first << ':'  << root->_kv.second << std::endl;
    
    			_InOrderTravel(root->_right);
    		}
    
    		void InOrderTravel() const
    		{
    			_InOrderTravel(_root);
    		}
    

    测试代码

    int main()
    {
    	lee::AVLTree<int, string> tree;
    	tree.Insert(make_pair(3, "php"));
    	tree.Insert(make_pair(1, "c++"));
    	tree.Insert(make_pair(2, "c#"));
    	tree.Insert(make_pair(7, "go"));
    	tree.Insert(make_pair(11, "js"));
    	tree.Insert(make_pair(19, "lua"));
    	tree.Insert(make_pair(5, "sql"));
    	tree.Insert(make_pair(8, "java"));
    	tree.Insert(make_pair(4, "python"));
    
    	tree.InOrderTravel();
    
    	return 0;
    }
    

    在这里插入图片描述
    这一部分测试是没有问题的。


    平衡判断

    		int countHeight(Node* root) const
    		{
    			if (root == nullptr)
    				return 0;
    
    			int leftHeight = countHeight(root->_left);
    			int rightHeight = countHeight(root->_right);
    
    			return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
    		}
    
    		bool _IsBalance(Node* root) const
    		{
    			if (root == nullptr)
    				return true;
    
    			int leftHeight = countHeight(root->_left);
    			int rightHeight = countHeight(root->_right);
    
    			return abs(leftHeight - rightHeight) < 2
    				&& _IsBalance(root->_left)
    				&& _IsBalance(root->_right);
    		}
    
    		bool IsBalance() const
    		{
    			return _IsBalance(_root);
    		}
    

    测试代码

    int main()
    {
    	lee::AVLTree<int, string> tree;
    	tree.Insert(make_pair(3, "php"));
    	tree.Insert(make_pair(1, "c++"));
    	tree.Insert(make_pair(2, "c#"));
    	tree.Insert(make_pair(7, "go"));
    	tree.Insert(make_pair(11, "js"));
    	tree.Insert(make_pair(19, "lua"));
    	tree.Insert(make_pair(5, "sql"));
    	tree.Insert(make_pair(8, "java"));
    	tree.Insert(make_pair(4, "python"));
    
    	cout << tree.IsBalance();
    
    	return 0;
    }
    

    在这里插入图片描述
    可以看到,这个特性也满足。说明这棵树的实现没有问题。


    AVL树的性能

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


    完整代码实现

    #pragma once
    #include<iostream>
    
    namespace lee
    {
    	template<class K, class V>
    	struct AVLTreeNode
    	{
    		AVLTreeNode(const std::pair<K, V>& kv)
    			: _left(nullptr)
    			, _right(nullptr)
    			, _parent(nullptr)
    			, _kv(kv)
    			, _bf(0)
    		{}
    
    		AVLTreeNode<K, V>* _left;
    		AVLTreeNode<K, V>* _right;
    		AVLTreeNode<K, V>* _parent;
    		std::pair<K, V> _kv;
    		int _bf;
    	};
    
    	template<class K, class V>
    	class AVLTree
    	{
    	public:
    		typedef AVLTreeNode<K, V> Node;
    
    		AVLTree() : _root(nullptr)
    		{}
    
    		~AVLTree()
    		{
    			destory(_root);
    		}
    
    		//右旋
    		void RotateR(Node* parent)
    		{
    			Node* subL = parent->_left;
    			Node* subLR = subL->_right;
    
    			parent->_left = subLR;
    
    			//如果subLR存在,则让他的父节点指向parent。
    			if (subLR)
    			{
    				subLR->_parent = parent;
    			}
    
    			subL->_right = parent;
    
    			Node* ppNode = parent->_parent;
    			parent->_parent = subL;
    
    			//两种情况
    			//如果parent为根节点,则让subL成为新的根节点
    			if (parent == _root)
    			{
    				_root = subL;
    				subL->_parent = nullptr;
    			}
    			//如果不是根节点,则改变subL与其祖父节点的指向关系
    			else
    			{
    				if (ppNode->_left == parent)
    				{
    					ppNode->_left = subL;
    				}
    				else
    				{
    					ppNode->_right = subL;
    				}
    
    				subL->_parent = ppNode;
    			}
    
    			//左旋完成后平衡,平衡因子归零。
    			subL->_bf = parent->_bf = 0;
    		}
    
    		//左旋
    		void RotateL(Node* parent)
    		{
    			Node* subR = parent->_right;
    			Node* subRL = subR->_left;
    
    			parent->_right = subRL;
    
    			if (subRL)
    			{
    				subRL->_parent = parent;
    			}
    
    			subR->_left = parent;
    			Node* ppNode = parent->_parent;
    			parent->_parent = subR;
    
    			if (parent == _root)
    			{
    				_root = subR;
    				subR->_parent = nullptr;
    			}
    			else
    			{
    				if (ppNode->_left == parent)
    				{
    					ppNode->_left = subR;
    				}
    				else
    				{
    					ppNode->_right = subR;
    				}
    
    				subR->_parent = ppNode;
    			}
    
    			subR->_bf = parent->_bf = 0;
    		}
    
    		//右左双旋
    		void RotateRL(Node* parent)
    		{
    			Node* subR = parent->_right;
    			Node* subRL = subR->_left;
    			//这里需要保存subRL的平衡因子,来调节旋转完后的平衡因子
    			int bf = subRL->_bf;
    
    			//先右单旋将折线结构转换为直线结构,也就是前面单旋就可以解决的问题。
    			RotateR(subR);
    			//然后再左单旋即可
    			RotateL(parent);
    
    			//根据subRL的bf来调节旋转后的平衡因子
    			if (bf == 1)
    			{
    				parent->_bf = -1;
    				subR->_bf = 0;
    				subRL->_bf = 0;
    			}
    			else if (bf == -1)
    			{
    				parent->_bf = 0;
    				subR->_bf = 1;
    				subRL->_bf = 0;
    			}
    			else
    			{
    				parent->_bf = 0;
    				subR->_bf = 0;
    				subRL->_bf = 0;
    			}
    		}
    
    
    		//左右双旋
    		void RotateLR(Node* parent)
    		{
    			Node* subL = parent->_left;
    			Node* subLR = subL->_right;
    
    			int bf = subLR->_bf;
    
    			RotateL(subL);
    			RotateR(parent);
    
    			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
    			{
    				parent->_bf = 0;
    				subL->_bf = 0;
    				subLR->_bf = 0;
    			}
    		}
    
    		bool Insert(const std::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
    				{
    					return false;
    				}
    			}
    
    			//插入节点
    			cur = new Node(kv);
    
    			//判断插入位置
    			if (cur->_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 (parent->_bf == 2)
    					{
    						//如果右边不平衡,并且子节点也是右边偏重,则左单旋
    						if (cur->_bf == 1)
    						{
    							RotateL(parent);
    						}
    						//如果右边不平衡,而子节点是左边偏重,此时需要先转换为上面的状态,先右单旋再左单旋。但是不能直接右单旋再左单旋,还需要根据情况处理平衡因子
    						else
    						{
    							RotateRL(parent);
    						}
    					}
    					else
    					{
    						//左边不平衡,并且子节点也是左边偏重,右单旋
    						if (cur->_bf == -1)
    						{
    							RotateR(parent);
    						}
    						//同上,左右双旋
    						else
    						{
    							RotateLR(parent);
    						}
    					}
    
    					//旋转完后恢复平衡,更新结束。
    					break;
    				}
    			}
    
    			return true;
    		}
    
    		Node* Find(const K& key)
    		{
    			//根据二叉搜索树的性质,从根节点出发,比根节点大则查找右子树,比根节点小则查找左子树
    			Node* cur = _root;
    
    			while (cur)
    			{
    				//比根节点大则查找右子树
    				if (key > cur->_kv.first)
    				{
    					cur = cur->_right;
    				}
    				//比根节点小则查找左子树
    				else if (key < cur->_kv.first)
    				{
    					cur = cur->_left;
    				}
    				//相同则返回
    				else
    				{
    					return cur;
    				}
    			}
    
    			//遍历完则说明查找不到,返回false
    			return nullptr;
    		}
    
    		bool erase(const K& key)
    		{
    			//删除直接按照二叉搜索树的规则删除,然后再进行平衡因子的更新即可
    			Node* cur = _root;
    			Node* parent = cur;
    
    			
    			/*
    				删除有三种情况,一种是删除叶子节点,可以直接删除
    				第二种情况,如果删除的节点只有一个子树,那么删除这个节点后,就让父节点指向他的这一子树
    				前两种情况可以合并处理
    
    				第三种情况则是左右子树都不为空,此时选择一个来节点来替换他后,再删除,就可以不破坏原有结构
    				如果要保持原有结构不变化,那么选择的节点必须要和删除节点在中序遍历中是连续的,而满足的只有两个节点,一个是其左子树的最大值,一个是其右子树的最小值。
    			*/
    			//删除部分
    			while (cur)
    			{
    				//找到删除的位置
    				if (key > cur->_kv.first)
    				{
    					parent = cur;
    					cur = cur->_right;
    				}
    				else if (key < cur->_kv.first)
    				{
    					parent = cur;
    					cur = cur->_left;
    				}
    				else
    				{
    					//前两种情况合并处理,如果当前结点只有一个子树,则让父节点指向他的子树
    					//处理只有右子树时					
    					if (cur->_left == nullptr)
    					{
    						//如果当前节点为根节点,则让右子树成为新的根节点
    						if (cur == _root)
    						{
    							_root = cur->_left;
    						}
    						else
    						{
    							//判断当前节点是他父节点的哪一个子树
    							if (parent->_right == cur)
    							{
    								parent->_right = cur->_right;
    							}
    							else
    							{
    								parent->_left = cur->_right;
    							}
    						}
    
    						delete cur;
    					}
    					//处理只有左子树时	
    					else if (cur->_right == nullptr)
    					{
    						//如果当前节点为根节点,则让左子树成为新的根节点
    						if (cur == _root)
    						{
    							_root = cur->_right;
    						}
    						else
    						{
    							if (parent->_right == cur)
    							{
    								parent->_right = cur->_left;
    							}
    							else
    							{
    								parent->_left = cur->_left;
    							}
    						}
    
    						delete cur;
    					}
    					//处理左右子树都不为空时,选取左子树的最右节点或者右子树的最左节点
    					else
    					{
    						//这里我选取的是左子树的最右节点
    
    						Node* LeftMax = cur->_left;
    						Node* LeftMaxParent = cur;
    
    						//找到左子树的最右节点
    						while (LeftMax->_right)
    						{
    							LeftMaxParent = LeftMax;
    							LeftMax = LeftMax->_right;
    						}
    
    						//替换节点
    						std::swap(cur->_kv, LeftMax->_kv);
    
    						//判断当前节点是他父节点的哪一个子树, 因为已经是最右子树了,所以这个节点的右子树为空,但是左子树可能还有数据,所以让父节点指向他的左子树
    						//并且删除最右节点
    						if (LeftMax == LeftMaxParent->_left)
    						{
    							LeftMaxParent->_left = LeftMax->_left;
    						}
    						else
    						{
    							LeftMaxParent->_right = LeftMax->_left;
    						}
    
    						delete LeftMax;
    					}
    
    					//删除成功,中断
    					break;
    				}
    			}
    
    			//查找不到
    			if (cur == nullptr)
    				return false;
    
    			//更新平衡因子
    			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 (parent->_bf == 2)
    					{
    						//如果右边不平衡,并且子节点也是右边偏重,则左单旋
    						if (cur->_bf == 1)
    						{
    							RotateL(parent);
    						}
    						//如果右边不平衡,而子节点是左边偏重,此时需要先转换为上面的状态,先右单旋再左单旋。但是不能直接右单旋再左单旋,还需要根据情况处理平衡因子
    						else
    						{
    							RotateRL(parent);
    						}
    					}
    					else
    					{
    						//左边不平衡,并且子节点也是左边偏重,右单旋
    						if (cur->_bf == -1)
    						{
    							RotateR(parent);
    						}
    						//同上,左右双旋
    						else
    						{
    							RotateLR(parent);
    						}
    					}
    
    					//旋转完后恢复平衡,更新结束。
    					break;
    				}
    			}
    
    			return true;
    		}
    
    		void _InOrderTravel(Node* root) const
    		{
    			if (root == nullptr)
    				return;
    
    			_InOrderTravel(root->_left);
    
    			std::cout << root->_kv.first << ':'  << root->_kv.second << std::endl;
    
    			_InOrderTravel(root->_right);
    		}
    
    		void InOrderTravel() const
    		{
    			_InOrderTravel(_root);
    		}
    
    		int countHeight(Node* root) const
    		{
    			if (root == nullptr)
    				return 0;
    
    			int leftHeight = countHeight(root->_left);
    			int rightHeight = countHeight(root->_right);
    
    			return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
    		}
    
    		bool _IsBalance(Node* root) const
    		{
    			if (root == nullptr)
    				return true;
    
    			int leftHeight = countHeight(root->_left);
    			int rightHeight = countHeight(root->_right);
    
    			return abs(leftHeight - rightHeight) < 2
    				&& _IsBalance(root->_left)
    				&& _IsBalance(root->_right);
    		}
    
    		bool IsBalance() const
    		{
    			return _IsBalance(_root);
    		}
    
    		void destory(Node*& root)
    		{
    			Node* node = root;
    			if (!root)
    				return;
    
    			destory(node->_left);
    			destory(node->_right);
    
    			delete node;
    			node = nullptr;
    		}
    	private:
    		Node* _root;
    	};
    };
    
    展开全文
  • 有序表的有所操作效率为O(logN),实现有序表的结构包括红黑树,AVL树,SizeBalance树简称SB树,跳表skiplist。 在时间复杂度层面,上面四种结构是一样的。 其中,红黑树、AVL树、SB树属于 同一个系列,那就是平衡...
  • AVL树详解

    2020-04-17 22:00:25
    AVL树 1 AVL树的定义 假设有一有序数组 1 2 3 4 5 6 7 8 9依次插入BST树,那么我们会发现这是一颗一直向右倾斜的树,已经成了链表形式。那么这样就失去了BST树的优势,原先 O(log⁡2n)O(\log_2n)O(log2​n)的效率就...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 35,510
精华内容 14,204
关键字:

avl树