【数据结构】AVL树

简介: 【数据结构】AVL树

前言

在C++的STL标准库中的set、map、multiset和multimap的底层实现都是二叉搜索树。

而进行搜索数据的方式有:

  1. 暴力搜索。
  2. 二分搜索(缺陷:二分查找需要有序数组,伴随着插入,维护的成本较高)
  3. 二叉搜索树(缺陷:在极端场景下会退化成链表,其效率无法保证)
  4. 二叉平衡搜索树
  5. 多叉平衡搜索树:B树
  6. 哈希表

由于二叉搜索树的自身缺陷,假如往树中插入的元素有序或者接近有序,二叉树就会退化成为单支树,时间复杂度为O(N),因此map、set等关联式容器的底层结构式对二叉树进行了平衡处理,及采用平衡树来实现。而AVL树的实现就是二叉平衡平衡树。

AVL树的概念

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

       在1962年,俩位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis发明了一种解决上述问题的方法:当向二叉搜索树中插入新节点后,如果可以保证每一个节点的左右子树高度之差的绝对值不超过1(需要对树中的节点进行调整),即可降低树的高度,从而减少平均搜索长度。

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

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

平衡因子的计算方式:平衡因子=右子树高度-左子树高度

AVL树的本质是高度平衡二叉搜索树,其二叉树只有在满二叉树的情况,平衡因子才会相等,否则进行增加与删除操作的时候,平衡因子肯定会被改变。

如果一棵二叉搜索树是高度平衡的,其就是AVL树。

如果有n个节点,其高度可以保持在O(logN),搜索时间复杂度O(logN)。

  • 当为满二叉树:2^h-1=N
  • AVL树:2^h-x= N(x的范围:1

AVL树的模拟实现

AVL树的基本框架

template<class k, class v>
struct AVLTreeNode
{
  //构造
  AVLTreeNode(const k& date)
    :_left(nullptr)
    , _right(nullptr)
    , _parent(nullptr)
    ,_kv(kv)
    ,_bf(0)
  {}
 
  AVLTreeNode<k, v>* _left;
  AVLTreeNode<k, v>* _right;
  AVLTreeNode<k, v>* _parent;
  pair<k,v> _kv;
  int _bf;//平衡因子
};
 
template<class k, class v>
class AVLTree
{
  typedef AVLTreeNode<k,v> Node;
  AVLTree()
    :_node(nullptr)
  {}
 
private:
  Node* _root;
};

AVL树的插入

       AVL树就是在二叉搜索树的基础之上引入了平衡因子,所以在插入节点的同时可以参考AVL树,然后通过结合平衡因子来解决问题。

其核心步骤分为三步:

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子
  3. 根据平衡因子观察是否达到平衡,如果没有则进行旋转。

插入新节点

  bool Insert(const pair<k,v>& kv)
  {
    //插入新节点
    if (_root == nullptr)
    {
      _root = new Node<kv>;
      return true;
    }
 
    Node* cur = _root;
    Node* parent = nullptr;
    while (cur)
    {
      if (cur->_kv.first > kv.first)
      {
        parent = cur;
        cur = cur->_left;
      }
      else if (cur->_kv.first < kv.first)
      {
        parent = cur;
        cur = cur->_right;
      }
      else
      {
        return false;
      }
    }
    cur = new Node<kv>;
    if (cur->_kv.first > kv.first)
    {
      parent->_left = cur;
    }
    else if (cur->_kv.first < kv.first)
    {
      parent->_right = cur;
    }
    else
    {
      assert(false);
    }
    cur->_parent = parent;
    //计算平衡因子
  }

平衡因子的计算

       当新的节点插入之后,AVL树的平衡性可能会被破坏,此时就需要更新破坏因子,并检测是否破坏了AVL树的平衡性。

  • 平衡因子=右子树的高度-左子树的高度。

观察这棵已经是AVL树的每一个平衡因子,当cur插入之后,parent的平衡因子一定会被调整,在插入cur之前,parent的平衡因子一共会分成三种情况:-1/0/1。

当插入cur之后,可能出现的结果如下:

  1. 新增在左,parent的平衡因子减1。
  2. 新增在右,parent的平衡因子加1。
  3. 更新后parent的平衡因子==0,说明parent所在的子树高度不变,不会影响祖先,不用再继续沿着root的路径往上更新。
  4. 更新后parent的平衡因子==1 or ==-1,说明parent所在的子树高度变化,会对祖先造成影响,需要再继续沿着到root的路径往上更新。
  5. 更新后parent的平衡因子==2 or ==-2,说明parent所在的子树高度已经变化,并且不平衡,需要立刻对parent所在的子树进行旋转,让其平衡。
  6. 平衡因子向上更新到根节点——>插入结束。

我们在更新平衡因子的时候可以发现,平衡因子相当于是用于检测这棵AVL树的平衡的,平衡因子的区间是[-2,2]。

  bool Insert(const pair<k,v>& kv)
  {
    //插入新节点
    if (_root == nullptr)
    {
      _root = new Node<kv>;
      return true;
    }
 
    Node* cur = _root;
    Node* parent = nullptr;
    while (cur)
    {
      if (cur->_kv.first > kv.first)
      {
        parent = cur;
        cur = cur->_left;
      }
      else if (cur->_kv.first < kv.first)
      {
        parent = cur;
        cur = cur->_right;
      }
      else
      {
        return false;
      }
    }
    cur = new Node<kv>;
    if (cur->_kv.first > kv.first)
    {
      parent->_left = cur;
    }
    else if (cur->_kv.first < kv.first)
    {
      parent->_right = cur;
    }
    else
    {
      assert(false);
    }
    cur->_parent = parent;
    //更新平衡因子
    while (parent)
    {
      //第一、二步:更新parent的平衡因子
      if (cur == parent->_left)
      {
        parent->_bf--;
      }
      else if (cur == parent->_right)
      {
        parent->_bf++;
      }
      //第三步:更新后parent的平衡因子==0
      if (parent->_bf == 0)
      {
        break;
      }
      //第四步:更新后parent的平衡因子==1 or ==-1
      else if (parent->_bf == 1 || parent->_bf == -1)
      {
        cur = parent;
        parent = cur->_parent;
      }
      //第五步:更新后parent的平衡因子==2 or ==-1
      else if (parent->_bf == 2 || parent->_bf == -2)
      {
        //旋转
        break;
      }
      else
      {
        assert(false);
      }
    }
  }

AVL树的旋转

       在一棵原本是是AVL平衡树中插入一个节点,可能会造成不平衡,此时必须调整树的结构,使之平衡。

       根据节点的插入位置的不同,AVL树的旋转会分为4种:

  1. 新节点插入较高左子树的左侧——右单旋
  2. 新节点插入较高右子树的右侧——左单旋
  3. 新节点插入较高左子树的右侧——先左单旋再右单旋
  4. 新节点插入较高右子树的左侧——先右单旋再左单旋

下面将进行详细讲解:

首先,旋转之前需要注意的俩个问题:

  • 保持其是搜索树
  • 变成平衡树且降低这个子树的高度。
左单旋

在上述的情况中,h的可能值为0,1,2,3,4...,a,b,c是符合AVL树的子树。

当h==2的时候,a和b是x,y,z中的一种,c只能是z,然后在这种情况下,才会导致M、N之间的旋转。

在插入前的可能形状的组合:3*3*1=9种;新元素可能插入的位置存在4种;这样当h==2的时候,合计情况有36种。

【注意】我们在插入新元素之后,会导致parent的平衡因子成为2,cur的平衡因子成为1,在这种情况下,需要左单旋转操作。

在进行左单旋操作的核心操作是:

  1. 将b转移到M的右子树之下;
  2. 如果b不等于空,将b的父亲转换为M;如果b为空,则不进行操作;
  3. 将M转移到N的左子树之下;
  4. 记录M的父亲
  5. 将M的父亲转换为N;
  6. 将记录的N的原父转换为N的父亲;并判断M是原父的左子还是右子。

这里可以总结:需要进行改变的数据一共有:parent的parent的孩子的指向,parent的右孩子的指向,parent的父亲指向,cur的左孩子指向,cur的父亲指向,cur的左孩子的父亲指向。

代码展示:

//左单旋
void RotateL(Node* parent)
{
  Node* cur = parent->_right;
  Node* curleft = cur->_left;
 
  parent->_right = curleft;
  if (curleft)
  {
    curleft->_parent = parent;
  }
  cur->_left = parent;
  Node* pparent = parent->_parent;
  parent->_parent = cur;
  if (pparent == nullptr)
  {
    _root = cur;
    cur->_parent = nullptr;
  }
  else
  {
    if (pparent->_left == parent)
    {
      pparent->_left = cur;
    }
    else //if(pparent->_right == parent)
    {
      pparent->_right = cur;
    }
    cur->_parent = pparent;
  }
  parent->_bf = cur->_bf = 0;
}

 

右单旋

右单旋与左单旋的方式类似,方式如图:

代码展示:

//右单旋
void RotateR(Node* parent)
{
  Node* cur = parent->_left;
  Node* curright = cur->_right;
  parent->_left = curright;
  if (curright)
  {
    curright->_parent = parent;
  }
  cur->_right = parent;
  Node* pparent = parent->_parent;
  parent->_parent = cur;
  if (pparent == nullptr)
  {
    _root = cur;
    cur->_parent = nullptr;
  }
  else
  {
    if (pparent->_left == parent)
    {
      pparent->_left = cur;
    }
    else
    {
      pparent->_right = cur;
    }
    cur->_parent = pparent;
  }
  parent->_bf = cur->_bf = 0;
}
右左旋转

       在了解完左单旋和右单选之后,我们基本对这俩种单旋转方式有了基本的认识,这俩种单旋的方式都是直线插入的,而在进行折线插入的时候需要实现另外一种方法。

【注意】我们可以在c位置的左子树位置或右子树位置添加节点。

当h==2的时候:

a和d是x,y,z中的任意一棵子树,c只能是z这棵子树;

这样合计下来:一共有3*3*4=36种情况。(3*3是a,d之前的组合;4是当h等于2的时候,可以新增的数据个数)。

此时可以发现,使用单旋的方式已经不能解决问题,需要使用双旋转的方式来解决问题:

  1. 以60为旋转点,进行右单旋转
  2. 以30为旋转点,进行左单旋转

右左双旋转的结果本质上是:

  • 60为根
  • 60的左边是30
  • 60的右边是90
  • 60的左边给了30的右边
  • 60的右边给了30的左边

通过对图例的了解,当h==0的时候,60的左右子树都为空,即60是新增的元素;当h>0的时候,在60的左子树或者60的右子树新增元素,才会构成右左旋转。

处理节点的平衡因子的时候,可以将平衡因子分成3份,判断的主要条件是60在旋转之前的的平衡因子:

  • 当60旋转之前的平衡因子为0,则旋转之后30,60,90的平衡因子都为0;
  • 当60旋转之前的平衡因子为1,则旋转之后60,90的平衡因子为0,30的平衡因子为-1;
  • 当60旋转之前的平衡因子为-1,则旋转之后60,30的平衡因子为0,90的平衡因子为1;

则进行代码展示:

  //左右旋转
  void RotateLR(Node* parent)
  {
    Node* cur = parent->_left;
    Node* curright = cur->_right;
    int bf = curright->_bf;
 
    RotateL(cur);
    RotateR(parent);
 
    if (bf == 0)
    {
      curright->_bf = 0;
      parent->_bf = 0;
      cur->_bf = 0;
    }
    else if (bf == 1)
    {
      curright->_bf = 0;
      parent->_bf = -1;
      cur->_bf = 0;
    }
    else if (bf == -1)
    {
      curright->_bf = 0;
      parent->_bf = 0;
      cur->_bf = 1;
    }
    else
    {
      assert(false);
    }
  }

 

左右旋转

左右旋转与右左旋转的思路相同,即:先对30进行左单选,然后再对90进行右单旋,旋转之后考虑平衡因子的更新。

处理节点的平衡因子的时候,可以将平衡因子分成3份,判断的主要条件是60在旋转之前的的平衡因子:

  • 当60旋转之前的平衡因子为0,则旋转之后30,60,90的平衡因子都为0;
  • 当60旋转之前的平衡因子为1,则旋转之后30,60的平衡因子为0,30的平衡因子为-1;
  • 当60旋转之前的平衡因子为-1,则旋转之后60,90的平衡因子为0,90的平衡因子为1;

则进行代码展示:

//右左旋转
void RotateRL(Node* parent)
{
  Node* cur = parent->_right;
  Node* curleft = cur->_left;
  int bf = curleft->_bf;
 
  RotateR(cur);
  RotateL(parent);
 
  if (bf == 0)
  {
    curleft->_bf = 0;
    parent->_bf = 0;
    cur->_bf = 0;
  }
  else if (bf == 1)
  {
    curleft->_bf = 0;
    parent->_bf = 0;
    cur->_bf = -1;
  }
  else if (bf == -1)
  {
    curleft->_bf = 0;
    parent->_bf = 0;
    cur->_bf = 1;
  }
  else
  {
    assert(false);
  }
}

AVL树的验证

      AVL树由于是再二叉搜索树的基础之上加入了平衡性的因素,因此如果需要验证AVL树,可以分为俩步:

1.验证是否为二叉搜索树:

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

2.验证是否为平衡树

  • 每一个节点子树的高度差的绝对值不会超过1(注意节点中如果没有平衡因子)
  • 节点的平衡因子的计算是否正确
  int Height(Node* root)
  {
    if (root == nullptr)
    {
      return 0;
    }
    int leftHeight = Height(root->_left);
    int rightHeight = Height(root->_right);
 
    return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
  }
 
  bool IsBalance()
  {
    return IsBalance(_root);
  }
 
  bool IsBalance(Node* root)
  {
    if (root == nullptr)
    {
      return true;
    }
    int leftHeight = Height(root->_left);
    int rightHeight = Height(root->_right);
 
    if (rightHeight - leftHeight != root->_bf)
    {
      cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
      return false;
    }
    
    return abs(rightHeight - leftHeight) < 2
      && IsBalance(root->_left)
      && IsBalance(root->_right);
        
  }

 

AVL树的删除

       由于AVL树也是二叉搜索树,可以按照二叉搜索树的方式将节点删除,步骤如下:

  1. 按照搜索树的规则查找节点删除
  2. 更新平衡因子,当cur为0的时候,更新parent的平衡因子
  3. 出现异常旋转。

AVL树的性能

       AVL树是一棵绝对平衡的二叉搜索树,要求每一个节点的左右子树高度之差的绝对值不能超过1,这样可以保证在查询的时候具有高效性,时间复杂度为logN。

       但是对于AVL树的一些结构修改的操作性能是非常低的,就比如:插入时需要维护其平衡,旋转的次数比较多,更差的是在删除的时候,有可能一直让旋转持续到根的位置。

代码展示

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
 
template<class k, class v>
struct AVLTreeNode
{
  //构造
  AVLTreeNode(const 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;
  pair<k,v> _kv;
  int _bf;//平衡因子
};
 
template<class k, class v>
class AVLTree
{
public:
  typedef AVLTreeNode<k,v> Node;
  AVLTree()
    :_root(nullptr)
  {}
 
  bool Insert(const pair<k,v>& kv)
  {
    //插入新节点
    if (_root == nullptr)
    {
      _root = new Node(kv);
      return true;
    }
 
    Node* cur = _root;
    Node* parent = nullptr;
    while (cur)
    {
      if (cur->_kv.first > kv.first)
      {
        parent = cur;
        cur = cur->_left;
      }
      else if (cur->_kv.first < kv.first)
      {
        parent = cur;
        cur = cur->_right;
      }
      else
      {
        return false;
      }
    }
    cur = new Node(kv);
    if (parent->_kv.first > kv.first)
    {
      parent->_left = cur;
    }
    else if (parent->_kv.first < kv.first)
    {
      parent->_right = cur;
    }
    else
    {
      assert(false);
    }
    cur->_parent = parent;
    //更新平衡因子
    while (parent)
    {
      //第一、二步:更新parent的平衡因子
      if (cur == parent->_left)
      {
        parent->_bf--;
      }
      else if (cur == parent->_right)
      {
        parent->_bf++;
      }
      //第三步:更新后parent的平衡因子==0
      if (parent->_bf == 0)
      {
        break;
      }
      //第四步:更新后parent的平衡因子==1 or ==-1
      else if (parent->_bf == 1 || parent->_bf == -1)
      {
        cur = parent;
        parent = cur->_parent;
      }
      //第五步:更新后parent的平衡因子==2 or ==-1
      else if (parent->_bf == 2 || parent->_bf == -2)
      {
        //旋转
        if (parent->_bf == 2 && cur->_bf == 1)
        {
          RotateL(parent);
        }
        else if (parent->_bf == -2 && cur->_bf == -1)
        {
          RotateR(parent);
        }
        else if (parent->_bf == 2 && cur->_bf == -1)
        {
          RotateRL(parent);
        }
        else if (parent->_bf == -2 && cur->_bf == 1)
        {
          RotateLR(parent);
        }
        break;
      }
      else
      {
        assert(false);
      }
    }
    return true;
  }
  //左单旋
  void RotateL(Node* parent)
  {
    Node* cur = parent->_right;
    Node* curleft = cur->_left;
 
    parent->_right = curleft;
    if (curleft)
    {
      curleft->_parent = parent;
    }
    cur->_left = parent;
    Node* pparent = parent->_parent;
    parent->_parent = cur;
    if (pparent == nullptr)
    {
      _root = cur;
      cur->_parent = nullptr;
    }
    else
    {
      if (pparent->_left == parent)
      {
        pparent->_left = cur;
      }
      else //if(pparent->_right == parent)
      {
        pparent->_right = cur;
      }
      cur->_parent = pparent;
    }
    parent->_bf = cur->_bf = 0;
  }
  //右单旋
  void RotateR(Node* parent)
  {
    Node* cur = parent->_left;
    Node* curright = cur->_right;
    parent->_left = curright;
    if (curright)
    {
      curright->_parent = parent;
    }
    cur->_right = parent;
    Node* pparent = parent->_parent;
    parent->_parent = cur;
    if (pparent == nullptr)
    {
      _root = cur;
      cur->_parent = nullptr;
    }
    else
    {
      if (pparent->_left == parent)
      {
        pparent->_left = cur;
      }
      else
      {
        pparent->_right = cur;
      }
      cur->_parent = pparent;
    }
    parent->_bf = cur->_bf = 0;
  }
  //左右旋转
  void RotateLR(Node* parent)
  {
    Node* cur = parent->_left;
    Node* curright = cur->_right;
    int bf = curright->_bf;
 
    RotateL(cur);
    RotateR(parent);
 
    if (bf == 0)
    {
      curright->_bf = 0;
      parent->_bf = 0;
      cur->_bf = 0;
    }
    else if (bf == 1)
    {
      curright->_bf = 0;
      parent->_bf = -1;
      cur->_bf = 0;
    }
    else if (bf == -1)
    {
      curright->_bf = 0;
      parent->_bf = 0;
      cur->_bf = 1;
    }
    else
    {
      assert(false);
    }
  }
  //右左旋转
  void RotateRL(Node* parent)
  {
    Node* cur = parent->_right;
    Node* curleft = cur->_left;
    int bf = curleft->_bf;
 
    RotateR(cur);
    RotateL(parent);
 
    if (bf == 0)
    {
      curleft->_bf = 0;
      parent->_bf = 0;
      cur->_bf = 0;
    }
    else if (bf == 1)
    {
      curleft->_bf = 0;
      parent->_bf = 0;
      cur->_bf = -1;
    }
    else if (bf == -1)
    {
      curleft->_bf = 0;
      parent->_bf = 0;
      cur->_bf = 1;
    }
    else
    {
      assert(false);
    }
  }
 
  int Height(Node* root)
  {
    if (root == nullptr)
    {
      return 0;
    }
    int leftHeight = Height(root->_left);
    int rightHeight = Height(root->_right);
 
    return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
  }
 
  bool IsBalance()
  {
    return IsBalance(_root);
  }
 
  bool IsBalance(Node* root)
  {
    if (root == nullptr)
    {
      return true;
    }
    int leftHeight = Height(root->_left);
    int rightHeight = Height(root->_right);
 
    if (rightHeight - leftHeight != root->_bf)
    {
      cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
      return false;
    }
    
    return abs(rightHeight - leftHeight) < 2
      && IsBalance(root->_left)
      && IsBalance(root->_right);
        
  }
private:
  Node* _root;
};


相关文章
|
18天前
|
存储 算法 搜索推荐
探索常见数据结构:数组、链表、栈、队列、树和图
探索常见数据结构:数组、链表、栈、队列、树和图
84 64
|
11天前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
13 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
|
11天前
|
存储 算法 数据管理
数据结构与算法学习二零:二叉排序树(BST)、平衡二叉树(AVL)
这篇文章通过需求分析、代码实现和测试验证,详细介绍了二叉排序树的创建、遍历和删除操作,以及二叉平衡树(AVL)的自平衡特性和单旋转操作,旨在提高树结构在数据管理中的效率和性能。
17 0
数据结构与算法学习二零:二叉排序树(BST)、平衡二叉树(AVL)
|
19天前
【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解(三)
【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解
|
8天前
|
Java C++
【数据结构】探索红黑树的奥秘:自平衡原理图解及与二叉查找树的比较
本文深入解析红黑树的自平衡原理,介绍其五大原则,并通过图解和代码示例展示其内部机制。同时,对比红黑树与二叉查找树的性能差异,帮助读者更好地理解这两种数据结构的特点和应用场景。
15 0
|
11天前
|
存储 算法
数据结构与算法学习十六:树的知识、二叉树、二叉树的遍历(前序、中序、后序、层次)、二叉树的查找(前序、中序、后序、层次)、二叉树的删除
这篇文章主要介绍了树和二叉树的基础知识,包括树的存储方式、二叉树的定义、遍历方法(前序、中序、后序、层次遍历),以及二叉树的查找和删除操作。
15 0
|
13天前
05(数据结构考研)树相关操作代码
05(数据结构考研)树相关操作代码
23 0
|
14天前
|
存储 算法 Java
数据结构和算法--分段树
数据结构和算法--分段树
11 0
|
16天前
【数据结构】翻转、平衡、对称二叉树,最大深度、判断两棵树是否相等、另一棵树的子树
【数据结构】翻转、平衡、对称二叉树,最大深度、判断两棵树是否相等、另一棵树的子树
18 0