浅析AVL树

本文详细阐述了AVL树的提出背景,包括平衡因子的定义及其对树结构的影响。深入讲解了二叉平衡树的插入、删除和查找操作,以及关键的旋转算法,包括单旋转和双旋转的实现。通过实例展示了如何确保树的平衡性,以及AVL树在提高搜索效率中的作用。
摘要由CSDN通过智能技术生成

1.为什么提出AVL树

学习完搜索二叉树以后,我们应该想到一个问题,如果我们的搜索二叉树的趋向于单链的形式,类似于:

这里写图片描述

2.二叉平衡树概念和结构

 

为了解决上述问题,所以提出了一个概念,叫做二叉平衡树。

二叉平衡树,相对于二叉搜索树,引入了一个叫做平衡因子的概念。
平衡因子:平衡因子就是右子树的深度-左子树的深度。

二叉平衡树的规则是:每一个节点的平衡因子的绝对值都要小于2。

所以我们需要给一个平衡因子。

二叉平衡树的结构:

template<typename K,typename V>
struct AVLBinaryTreeNode
{
    typedef AVLBinaryTreeNode<K, V> Node;
    AVLBinaryTreeNode(const K& key,const V& value)
        :_left(NULL)
        ,_right(NULL)
        , _parent(NULL)
        ,_key(key)
        , _bf(0)
        , _value(value)
    {}
    Node* _left;
    Node* _right;
    Node* _parent;

    int _bf;    //来保存右子树高度-左子树高度,平衡因子。
    K _key;
    V _value;
};


3.二叉平衡树的平衡化旋转

对于二叉平衡树来说,最重要的就是它的旋转算法,因为他要保证所有节点的平衡因子保持在0,1,-1,所以当平衡因子为2或者-2的时候,我们需要调整,这个时候就引入了旋转这个概念。

例如:

这里写图片描述

比如上面的这个例子,我们可以看出来在这我们的3已经不满足二叉平衡树的条件了,所以在这里我们应该进行旋转。

另外在这里需要知道,对于叶子节点,它的平衡因子都是0。

单旋转

首先我们来看单旋转,单旋转分为两种情况:左单旋和右单旋

这里写图片描述

示例代码:

//左旋
    void _RotateL(Node *parent)
    {
        assert(parent);

        Node* SubR = parent->_right;
        Node* SubRL = SubR->_left;
        parent->_right = SubRL;
        if (SubRL)
            SubRL->_parent = parent;
        Node * ppNode = parent->_parent;            //记录根的父亲    
        SubR->_left = parent;       
        parent->_parent = SubR;
        if (ppNode==NULL)                       //考虑根节点的情况
        {
            _root = SubR;
            SubR->_parent = NULL;
        }
        else
        {
            if (ppNode->_left == parent)           //判断旋转以后的根应该链接在根的父亲的那边
            {
                ppNode->_left = SubR;
            }
            else
            {
                ppNode->_right = SubR;
            }
            SubR->_parent = ppNode;
        }

        parent->_bf = SubR->_bf = 0;              //重置平衡因子

    }
//右旋
    void _RotateR(Node *parent)
    {
        Node *SubL = parent->_left;
        Node *SubLR = SubL->_right;
        parent->_left = SubLR;
        if (SubLR)                  
        {
            SubLR->_parent = parent;
        }
        Node* ppNode = parent->_parent;         //记录根节点父亲
        SubL->_right = parent;
        parent->_parent = SubL;
        if (ppNode == NULL)                     //考虑根节点情况
        {
            _root = SubL;
            SubL->_parent = NULL;
        }
        else
        {
            if (ppNode->_left == parent)            //判断旋转以后的根应该链接在根的父亲的那边
            {
                ppNode->_left = SubL;
            }
            else
            {
                ppNode->_right = SubL;
            }
            SubL->_parent = ppNode;
        }


        parent->_bf = SubL->_bf = 0;               //重置平衡因子
    }

双旋转

平衡二叉树有时的旋转是双旋转,双旋转有一个特点,就是他的parent节点和下一个节点平衡因子需要异号。

这里写图片描述

在这,根据sublr的平衡因子不同,分为了3种情况进行插入。接下来,我们对每一种情况进行分析,讲解。

第一种情况:
是sublr的平衡因子为0,这个时候就可以把sublr当作一个要插入的叶子节点来理解,这样subl的平衡因子为1,parentde平衡因子为-2,这样,就构成了双旋转。

这里写图片描述

第二种情况:
sublr的平衡因子为-1,这个时候就是在sublr的左子树进行插入节点操作,这样,subl的平衡因子变为1,parent的平衡因子变为-2。

这里写图片描述

 

第三种情况:
第三种情况所说的就是sublr的平衡因子为1,这个时候插入点是sublr的右树。这样,subl的平衡因子变为1,parent的平衡因子变为-2。

这里写图片描述

上述的就是进行右左双旋时的三种情况,我们可以画一个表格总结下:

这里写图片描述

示例代码:

//左右双旋
    void _RotateLR(Node *parent)
    {
        //进行双旋转的时候通过在这里的旋转以后的根节点的bf进行判断
        Node* SubL = parent->_left;
        Node* SubLR = SubL->_right;
        //计算旋转以后的根节点的bf
        int bf = SubLR->_bf;

        _RotateL(SubL);
        _RotateR(parent);
        //如果bf为0,代表插入点就是这个节点,这个时候所有旋转后的bf皆为0
        if (bf == 0)
        {
            SubL->_bf = parent->_bf = 0;
        }
        //当bf为1时,这个时候相当于是在给bf的右树插入,所以插入以后subL旋转后的右边高度为h,

        else if (bf == 1)
        {
            SubL->_bf = -1;
            parent->_bf = 0;
        }
        //当bf为-1,这时相当于是给bf的左树进行插入
        else
        {
            SubL->_bf = 0;
            parent->_bf = 1;
        }
        SubLR->_bf = 0;
    }

右左双旋转

接下来我们进行另外一种双旋转的分析,右左双旋,有了上面分析的基础,我相信,下面的分析,你一看就能理解,通过单旋转,我们就可以看到,旋转是镜像的

这里写图片描述

 

- subrl的bf==0

这里写图片描述

- subrl的bf==1

这里写图片描述

  • subrl的bf==-1

这里写图片描述

由于右左旋转和上述的左右旋转分析的思路是一样的,所以我也不就在太详细的介绍了。

 

void _RotateRL(Node *parent)
    {
        //双旋转根据插入不同进行调整pf,
        Node* SubR = parent->_right;
        Node* SubRL = SubR->_left;
        //根据最后的根节点的bf进行调整
        int bf = SubRL->_bf;

        _RotateR(SubR);
        _RotateL(parent);
        //当bf为0,代表插入的就是他这个节点,所有的bf都变作0
        if (bf == 0)
        {
            parent->_bf = SubR->_bf = 0;
        }
        //当bf为-1,代表这时是在这个根的左树插入,h-1高度变作h。
        else if (bf == -1)
        {
            parent->_bf = 0;
            SubR->_bf = 1;
        }
        //当bf为1时,代表是在这个旋转后的根的右树插入,h-1的高度作h。
        else
        {
            parent->_bf = -1;
            SubR->_bf = 0;
        }
        SubRL->_bf = 0;


    }

4.二叉平衡树的插入和删除

平衡二叉树的插入算法,和搜索二叉树的插入算法类似,依然是查找到为空的地方,然后进行创建新节点,进行插入,不过这里多了一个parent指针,所以要记得维护,又因为平衡二叉树的特性,所以不能忘了要从插入节点向上检查,确保向上始终为平衡二叉树,否则,需要进行平衡化旋转。

//插入算法
bool Insert(const K& key,const V& value)
    {
        if (_root == NULL)
        {
            _root = new Node(key, value);
            return true;
        }
        Node* parent = NULL;
        Node* cur = _root;
        while (cur)
        {
            if (cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (cur->_key>key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                return false;
            }
        }
        cur = new Node(key, value);
        if (parent->_key > key)
        {
            parent->_left = cur;
            cur->_parent = parent;
        }
        else
        {
            parent->_right = cur;
            cur->_parent = parent;
        }
        while (parent)
        {
            if (parent->_left == cur)
            {
                parent->_bf--;
            }
            else
            {
                parent->_bf++;
            }
            if (parent->_bf == 0)
            {
                return true;
            }
            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);
                        return true;
                    }
                    else
                    {
                        _RotateRL(parent);

                        //parent->_bf = _Height(parent->_right) - _Height(parent->_left);
                        //_RotateRL(parent);
                        return true;

                    }
                }
                if (parent->_bf == -2)
                {
                    if (cur->_bf == 1)
                    {
                        _RotateLR(parent);


                        //
                        旋转完以后必须进行重新对parent配置_bf
                        //parent->_bf = _Height(parent->_right) - _Height(parent->_left);
                        return true;

                    }
                    else
                    {
                        _RotateR(parent);
                        return true;

                    }
                }
            }
        }
        return true;
    }

至于删除算法,因为删除的很复杂,在书上看到的是如果在你删除的节点数少与一半的时候,这个时候推荐使用懒惰删除,

规则就是:简单的标记要删除的节点是为已删除。然后在查找等例程里面都人为的忽视标记过的节点。如果标记删除的节点大于总数一半,就进行一次遍历,删除所有标记节点。

5.二叉平衡树的查找

AVL树的

    bool Find(const K& key)
    {
        if (_root == NULL)
            return false;
        Node *cur = _root;
        while (cur)
        {
            if (key < cur->_key)
            {
                cur = cur->_left;
            }
            else if (key>cur->_key)
            {
                cur = cur->_right;
            }
            else
            {
                return true;
            }
        }
        return false;
    }

查找和搜索二叉树的类似,在这里我就不多说了,直接晒代码。因为在你插入中已经有这方面的逻辑了。

6.总结

AVL树是为了解决二叉搜素树中类似链式的树的时间复杂度太高的问题,如果采用了AVL树,大致时间复杂度都可以变为O(logN),这样就更加高效了。AVL树种最关键的就是它的旋转,分为单旋转和多旋转。AVL树是后续学习红黑树的基础。

#pragma once

#include<iostream>
#include<cstdlib>
#include<cassert>

using namespace std;

template<typename K,typename V>
struct AVLBinaryTreeNode
{
    typedef AVLBinaryTreeNode<K, V> Node;
    AVLBinaryTreeNode(const K& key,const V& value)
        :_left(NULL)
        ,_right(NULL)
        , _parent(NULL)
        ,_key(key)
        , _bf(0)
        , _value(value)
    {}
    Node* _left;
    Node* _right;
    Node* _parent;

    int _bf;    //来保存左右子树的高度差,平衡因子。
    K _key;
    V _value;
};

template<typename K, typename V>
class AVLBinaryTree
{
public:
    typedef AVLBinaryTreeNode<K, V> Node;
public:
    //构造函数
    AVLBinaryTree()
        :_root(NULL)
    {
    }
    //析构函数
    ~AVLBinaryTree()
    {
        _DestoryTree(_root);
    }
    //拷贝构造
    AVLBinaryTree(const AVLBinaryTree<K, V> & a)
    {
        _root = _Copy(_root);
    }
    //现代写法赋值
    AVLBinaryTree<K,V>operator =(const AVLBinaryTree<K,V>& d)
    {
        if (this != &d)
        {
            AVLBinaryTree<K,V> tmp(d);
            std::swap(tmp._root,_root);
        }
            return *this;
    }
    //插入节点
    bool Insert(const K& key,const V& value)
    {
        if (_root == NULL)
        {
            _root = new Node(key, value);
            return true;
        }
        Node* parent = NULL;
        Node* cur = _root;
        while (cur)
        {
            if (cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (cur->_key>key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                return false;
            }
        }
        cur = new Node(key, value);
        if (parent->_key > key)
        {
            parent->_left = cur;
            cur->_parent = parent;
        }
        else
        {
            parent->_right = cur;
            cur->_parent = parent;
        }
        while (parent)
        {
            if (parent->_left == cur)
            {
                parent->_bf--;
            }
            else
            {
                parent->_bf++;
            }
            if (parent->_bf == 0)
            {
                return true;
            }
            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);
                        return true;
                    }
                    else
                    {
                        _RotateRL(parent);

                        //parent->_bf = _Height(parent->_right) - _Height(parent->_left);
                        //_RotateRL(parent);
                        return true;

                    }
                }
                if (parent->_bf == -2)
                {
                    if (cur->_bf == 1)
                    {
                        _RotateLR(parent);


                        //
                        旋转完以后必须进行重新对parent配置_bf
                        //parent->_bf = _Height(parent->_right) - _Height(parent->_left);
                        return true;

                    }
                    else
                    {
                        _RotateR(parent);
                        return true;

                    }
                }
            }
        }
        return true;
    }

    //查找节点
    bool Find(const K& key)
    {
        if (_root == NULL)
            return false;
        Node *cur = _root;
        while (cur)
        {
            if (key < cur->_key)
            {
                cur = cur->_left;
            }
            else if (key>cur->_key)
            {
                cur = cur->_right;
            }
            else
            {
                return true;
            }
        }
        return false;
    }
    //判断是否为AVL树
    //bool IsAVLBinaryTree()
    //{
    //  return _IsAVLBinaryTree(_root);
    //}
    bool IsAVLBinaryTree()
    {
        int depth = 0;
        return _IsAVLBinaryTree(_root, depth);

    }
    //中序遍历
    void Inorderprint()
    {
        _Inorderprint(_root); 
        cout << endl;
    }
    //得到高度
    size_t Height() const
    {
        _Height(_root);
    }
protected:
    //拷贝构造二叉树
    Node* _Copy(Node *root)
    {
        Node * NewNode = NULL;
        if (root != NULL)
        {
            NewNode = new Node(root->_value);
            NewNode->_left = _Copy(root->_left);
            NewNode->_right = _Copy(root->_right);
        }
        return NewNode;
    }
    //树的高度
    size_t _Height(Node *root)
    {
        if (root == NULL)
            return 0;
        size_t lheight = _Height(root->_left) + 1;
        size_t rheight = _Height(root->_right) + 1;

        return lheight > rheight ? lheight : rheight;
    }
    //判断二叉树是否为平衡二叉树
    /*bool _IsAVLBinaryTree(Node *root)
    {
        if (root==NULL)
        {
            return true;
        }

        int heightorder = abs((int)(_Height(root->_right) - _Height(root->_left)));

        return ((heightorder<2)
            &&(_IsAVLBinaryTree(root->_left))
            && (_IsAVLBinaryTree(root->_right)));
    }*/
    //判断二叉树是否为平衡二叉树,优化版本
    bool _IsAVLBinaryTree(Node *root,  int & depth)
    {
        //如果为空,往父节点返
        if (root == NULL)
        {
            depth = 0;
            return true;
        }
        //记录左节点和右节点深度
        int left = 0;
        int right = 0;
        //采取传引用的方式,下层递归进行对深度修改以后会影响上一层
        if (_IsAVLBinaryTree(root->_left, left) && _IsAVLBinaryTree(root->_right, right))
        {
            //计算平衡因子
            int pf = right - left;
            //判断平衡因子是否合法
            if (pfIsInvaild(pf))
            {
    //合法就让自身加上子树的深度,然后因为是传引用,所以当递归回到上一层的时候,上层中的right和left就是左右子树的深度
                depth = 1 + (right > left ? right : left);
                return true;
            }
        }
        return false;
    }
    //判断平衡因子是否合法
    bool pfIsInvaild(const int& pf)
    {
        return abs(pf) < 2;
    }
    //中序递归遍历
    void _Inorderprint(Node *root)
    {
        if (root == NULL)
        {
            return;
        }
        _Inorderprint(root->_left);
        cout << root->_key<<" ";
        cout << root->_bf << endl;
        _Inorderprint(root->_right);
    }
    //销毁AVL树
    Node* _DestoryTree(Node*& root)
    {
        if (root != NULL)
        {
            root->_left = _DestoryTree(root->_left);
            root->_right = _DestoryTree(root->_right);
            delete root;
            root = NULL;
        }
        return  root;
    }
    //左旋
    void _RotateL(Node *parent)
    {
        assert(parent);

        Node* SubR = parent->_right;
        Node* SubRL = SubR->_left;
        parent->_right = SubRL;
        if (SubRL)
            SubRL->_parent = parent;
        Node * ppNode = parent->_parent;            //记录根的父亲    
        SubR->_left = parent;       
        parent->_parent = SubR;
        if (ppNode==NULL)                       //考虑根节点的情况
        {
            _root = SubR;
            SubR->_parent = NULL;
        }
        else
        {
            if (ppNode->_left == parent)           //判断旋转以后的根应该链接在根的父亲的那边
            {
                ppNode->_left = SubR;
            }
            else
            {
                ppNode->_right = SubR;
            }
            SubR->_parent = ppNode;
        }

        parent->_bf = SubR->_bf = 0;              //重置平衡因子

    }
    //先左旋后右旋
    //void _RotateLR(Node *parent)
    //{
    //  Node* SubL = parent->_left;
    //  Node* SubLR = SubL->_right;
    //  SubL->_right = SubLR->_left;
    //  if (SubLR->_left)
    //      SubL->_right->_parent = SubL;
    //  SubLR->_left = SubL;
    //  SubL->_parent = SubLR;
    //  parent->_left = SubLR;
    //  SubLR->_parent = parent;
    //  //判断SubLR的左子树是否存在,如果存在,在调整过程当中,需要进行bf的调整
    //  //SubLR的left最后是变成了SubL的left,所以当pf为1时,这个时候调整过去,SubL的pf就是左树比右树大。
    //  if (SubLR->_bf <= 0)
    //      SubL->_bf = 0;
    //  else
    //      SubL->_bf = -1;
    //  parent->_left = SubLR->_right;
    //  if (SubLR->_right)
    //  {
    //      parent->_left->_parent = parent;
    //  }
    //  Node *ppnode = parent->_parent;
    //  SubLR->_right = parent;
    //  parent->_parent = SubLR;
    //  if (ppnode == NULL)
    //  {
    //      _root = SubLR;
    //      SubLR->_parent = NULL;
    //  }
    //  else
    //  {
    //      if (parent == ppnode->_left)
    //      {
    //          ppnode->_left = SubLR;
    //      }
    //      else
    //      {
    //          ppnode->_right = SubLR;
    //      }
    //      SubLR->_parent = ppnode;
    //  }
    //  //同上,这是看SubLR的right是否存在。
    //  if (SubLR->_bf == -1)
    //      parent->_bf = 1;
    //  else
    //      parent->_bf = 0;
    //  SubLR->_bf = 0;
    //}
    void _RotateLR(Node *parent)
    {
        //进行双旋转的时候通过在这里的旋转以后的根节点的bf进行判断
        Node* SubL = parent->_left;
        Node* SubLR = SubL->_right;
        //计算旋转以后的根节点的bf
        int bf = SubLR->_bf;

        _RotateL(SubL);
        _RotateR(parent);
        //如果bf为0,代表插入点就是这个节点,这个时候所有旋转后的bf皆为0
        if (bf == 0)
        {
            SubL->_bf = parent->_bf = 0;
        }
        //当bf为1时,这个时候相当于是在给bf的右树插入,所以插入以后subL旋转后的右边高度为h,

        else if (bf == 1)
        {
            SubL->_bf = -1;
            parent->_bf = 0;
        }
        //当bf为-1,这时相当于是给bf的左树进行插入
        else
        {
            SubL->_bf = 0;
            parent->_bf = 1;
        }
        SubLR->_bf = 0;
    }
    //先右旋后左旋
    //void _RotateRL(Node *parent)
    //{
    //  Node *SubR = parent->_right;
    //  Node *SubRL = SubR->_left;
    //  //右旋
    //  SubR->_left = SubRL->_right;
    //  if (SubRL->_right)
    //      SubR->_right->_parent = SubR;
    //  parent->_right = SubRL;
    //  SubRL->_parent = parent;
    //  SubRL->_right = SubR;
    //  SubR->_parent = SubRL;
    //  if (SubRL->_bf >= 0)
    //      SubR->_bf = 0;
    //  else
    //      SubR->_bf = 1;
    //  //左旋
    //  parent->_right = SubRL->_left;
    //  if (SubRL->_left)
    //      parent->_right->_parent = parent;
    //  Node *ppNode = parent->_parent;
    //  SubRL->_left = parent;
    //  parent->_parent = SubRL;
    //  if (ppNode == NULL)
    //  {
    //      _root = SubRL;
    //      SubRL->_parent = NULL;
    //  }
    //  else
    //  {
    //      if (ppNode->_left == parent)
    //      {
    //          ppNode->_left = SubRL;
    //      }
    //      else
    //      {
    //          ppNode->_right = SubRL;
    //      }
    //      SubRL->_parent = ppNode;
    //  }
    //  if (SubRL->_bf == 1)
    //      parent->_bf = -1;
    //  else
    //      parent->_bf = 0;
    //}
    //右旋
    void _RotateRL(Node *parent)
    {
        //双旋转根据插入不同进行调整pf,
        Node* SubR = parent->_right;
        Node* SubRL = SubR->_left;
        //根据最后的根节点的bf进行调整
        int bf = SubRL->_bf;

        _RotateR(SubR);
        _RotateL(parent);
        //当bf为0,代表插入的就是他这个节点,所有的bf都变作0
        if (bf == 0)
        {
            parent->_bf = SubR->_bf = 0;
        }
        //当bf为-1,代表这时是在这个根的左树插入,h-1高度变作h。
        else if (bf == -1)
        {
            parent->_bf = 0;
            SubR->_bf = 1;
        }
        //当bf为1时,代表是在这个旋转后的根的右树插入,h-1的高度作h。
        else
        {
            parent->_bf = -1;
            SubR->_bf = 0;
        }
        SubRL->_bf = 0;


    }
    //右旋
    void _RotateR(Node *parent)
    {
        Node *SubL = parent->_left;
        Node *SubLR = SubL->_right;
        parent->_left = SubLR;
        if (SubLR)                  
        {
            SubLR->_parent = parent;
        }
        Node* ppNode = parent->_parent;         //记录根节点父亲
        SubL->_right = parent;
        parent->_parent = SubL;
        if (ppNode == NULL)                     //考虑根节点情况
        {
            _root = SubL;
            SubL->_parent = NULL;
        }
        else
        {
            if (ppNode->_left == parent)            //判断旋转以后的根应该链接在根的父亲的那边
            {
                ppNode->_left = SubL;
            }
            else
            {
                ppNode->_right = SubL;
            }
            SubL->_parent = ppNode;
        }


        parent->_bf = SubL->_bf = 0;               //重置平衡因子
    }
protected:
    Node* _root;
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值