【AVL树的模拟实现】

简介: 【AVL树的模拟实现】

1 AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

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

它的左右子树都是AVL树

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

ece7544a341f42db8ea4b70d3c10fd65.png

叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在O ( l o g 2 n ) O(log_2 n)O(log

2

n),搜索时间复杂度O ( l o g 2 n ) O(log_2 n)O(log

2

n).

2 AVL树结点的定义

代码:

template<class K,class V>
class AVLNode
{
public:
  AVLNode<K, V>* _left;
  AVLNode<K, V>* _right;
  AVLNode<K, V>* _parent;
  pair<K, V> _kv;
  int _bf;//balance factory (右边++,左边--)
public:
  AVLNode(const pair<K, V> kv)
    :_left(nullptr)
    , _right(nullptr)
    , _parent(nullptr)
    , _kv(kv)
    , _bf(0)
  {}
};
template<class K, class V>
class AVLTree
{
  typedef AVLNode<K, V> Node;
private:
  Node* _root=nullptr;
};

这个跟我们讲解的普通搜索二叉树的思路几乎是一样的,很容易理解。


3 AVL树的插入

这是我们今天要讲解的重点,也是难点。实现AVL树的方式有很多,博主采用的是平衡因子加三叉链这种方式来实现,因为相对于其他方式这种方式的理解要稍微简单些。

首先基本的框架搭建好:

bool insert(const pair<K, V> kv)
  {
    if (_root == nullptr)
    {
      _root = new Node(kv);
      return true;
    }
    Node* parent = nullptr;
    Node* cur = _root;
    while (cur)
    {
      if (cur->_kv.first > kv.first)
      {
        parent = cur;
        cur = cur->_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;
      cur->_parent = parent;
    }
    else
    {
      parent->_right = cur;
      cur->_parent = parent;
    }
      return true;
  }

这个是我们讲解普通二叉搜索树玩剩下的,很好理解。

现在的关键是如何更新平衡因子?

我们不难发现:

当插入在parent左边时,平衡因子–,插入在parent右边时平衡因子++;

插入后parent的平衡因子为0,便不用向上更新了,如果为-1/1,便还要向上更新;

当parent的平衡因子为-2/2时说明已经出问题了,我们就要使出旋转大法修正,旋转完毕后便不用向上更新了。

代码解释:

//更新平衡因子
    while (parent)
    {
      if (cur == parent->_left)
        parent->_bf--;
      else
        parent->_bf++;
      if (parent->_bf == 0)
        break;
      else if (parent->_bf == 1 || parent->_bf == -1)
      {
        parent = parent->_parent;
        cur = cur->_parent;
      }
      else if (parent->_bf == 2 || parent->_bf == -2)
      {
        //已经出错了,需要旋转处理
        if (parent->_bf == -2 && cur->_bf == -1)
        {
          //右单旋
          RotateR(parent);
        }
        else if (parent->_bf == 2 && cur->_bf == 1)
        {
          //左单旋
          RotateL(parent);
        }
        else if (parent->_bf == 2 && cur->_bf == -1)
        {
          //先右单旋,再左单旋
          RotateRL(parent);
        }
        else if (parent->_bf == -2 && cur->_bf == 1)
        {
          //先左单旋,再右单旋
          RotateLR(parent);
        }
        else
        {
          assert(false);
        }
        break;//旋转完毕已经平衡了,记得break出去
      }
      else
      {
        assert(false);
      }
    }

其中出错要旋转不外乎份4种情况:左单旋,右单旋,左右双旋,右左双旋

我们一个一个来看:

3.1 右单旋

抽象图是这样的:

87faa8018d834bbeaf20d6ac8368ffcf.png

看着也很好理解,这样旋转后30 60 的平衡因子都变成了0,整个树也是平衡的。

我画了一个具象图来帮助大家理解:

h==0时:

3a40341687d64b918d8945802854f35e.png

h==1时:

4835b21f0819495cb348cfd4c2fd642c.png

h==2时:

ff8e9858293144d8acabcacda85c4c35.png

代码实现:

void RotateR(Node* parent)
  {
    Node* childL = parent->_left;
    Node* childLR = childL->_right;
    Node* grand = parent->_parent;
    parent->_left = childLR;
    if (childLR)
      childLR->_parent = parent;
    childL->_right = parent;
    parent->_parent = childL;
    //别忘了,还要链接grand与childL的关系
    if (grand == nullptr)
    {
      _root = childL;
      childL->_parent = nullptr;
    }
    else
    {
      if (grand->_left == parent)
        grand->_left = childL;
      else
        grand->_right = childL;
      childL->_parent = grand;
    }
    //更新平衡因子
    parent->_bf = childL->_bf = 0;
  }

3.2 左单旋

左单旋和右单旋类似,只是换了下位置,这里我就只给出抽象图了,不画具象图:


a86f950013e44ae6a5271286bae4782b.png

代码实现:

void RotateL(Node* parent)
  {
    Node* childR = parent->_right;
    Node* childRL = childR->_left;
    Node* grand = parent->_parent;
    parent->_right = childRL;
    if (childRL)
      childRL->_parent = parent;
    childR->_left =parent ;
    parent->_parent = childR;
    if (grand == nullptr)
    {
      _root = childR;
      childR->_parent = nullptr;
    }
    else
    {
      if (grand->_left == parent)
        grand->_left = childR;
      else
        grand->_right = childR;
      childR->_parent = grand;
    }
    parent->_bf = childR->_bf = 0;
  }

3.3 右左双旋

像下面这种情况,我们只是用单旋是解决不了问题的,要用双旋解决:

d4f82545c70249eea166f6740aa2124a.png


先以90结点右单旋转化成了我们前面讲的单旋问题,再以30左单旋即可,但是要注意分析插入后未旋转前新节点后60的平衡因子,因为60的平衡因子不同我们旋转后所更新结点的平衡因子就有所差异。注意插入后60的平衡因子可能为0.(这里建议大家画图分析)

代码实现:

void RotateRL(Node* parent)
  {
    Node* childR = parent->_right;
    Node* childRL = childR->_left;
    int bf = childRL->_bf;
    RotateR(childR);
    RotateL(parent);
    //更新平衡因子
    childRL->_bf = 0;
    if (bf == -1)
    {
      childR->_bf = 1;
      parent->_bf = 0;
    }
    else if (bf == 1)
    {
      parent->_bf = -1;
      childR->_bf = 0;
    }
    else if (bf == 0)
    {
      ;
    }
    else
    {
      assert(false);
    }
  }

3.4 左右双旋

跟右左双旋类似,这里就不在多讲了:


58e9b57abb33456092405ca5a1383bcc.png

代码实现:

void RotateLR(Node* parent)
  {
    Node* childL = parent->_left;
    Node* childLR = childL->_right;
    int bf = childLR->_bf;
    RotateL(childL);
    RotateR(parent);
    //更新平衡因子
    childLR->_bf = 0;
    if (bf == -1)
    {
      childL->_bf = 0;
      parent->_bf = 1;
    }
    else if (bf == 1)
    {
      parent->_bf = 0;
      childL->_bf = -1;
    }
    else if (bf == 0)
    {
      ;
    }
    else
    {
      assert(false);
    }
  }

4 AVL树的验证

代码:

private:
  Node* _root=nullptr;
  void _inorder(Node* root)
  {
    if (root == nullptr)
      return;
    _inorder(root->_left);
    cout << root->_kv.first << ":" << root->_kv.second << endl;
    _inorder(root->_right);
  }
  size_t _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(Node* root)
  {
    if (root == nullptr)
      return true;
    int leftHeight = _height(root->_left);
    int rightHeight = _height(root->_right);
    return (abs(leftHeight - rightHeight) <= 1) && _isbalance(root->_left) && _isbalance(root->_right);
  }
public:
  void inorder()
  {
    _inorder(_root);
  }
  size_t height()
  {
    return _height(_root);
  }
  bool isbalance()
  {
    return _isbalance(_root);
  }

平衡二叉树的验证我们很早就讲过了,所以相信大家能够很轻易看懂代码。测试时还可以根据左右子树高度差来判断平衡因子的正确性,大家可以自己下去试试。

大家下去可以用一些随机数来测测。

有需要的老铁可以去博主码云里看看:

点击这里

好了,今天的分享就到这里了,我们下期再见。

目录
相关文章
|
2月前
|
存储 关系型数据库 Java
红黑树,B+树,B树的原理
红黑树(Red-Black Tree)、B树(B-Tree)和 B+树(B+ Tree)都是自平衡的树结构,用于高效地进行查找、插入和删除操作。它们在数据库和文件系统等应用中有广泛的应用。
41 2
|
4月前
深入解析AVL树:高效实现二叉平衡搜索树(2)
深入解析AVL树:高效实现二叉平衡搜索树
18 1
|
4月前
|
存储
深入解析AVL树:高效实现二叉平衡搜索树
深入解析AVL树:高效实现二叉平衡搜索树
19 1
|
5月前
AVL树的原理以及实现
AVL树的原理以及实现
36 4
|
5月前
AVL树的原理及其实现
AVL树的原理及其实现
|
5月前
|
存储 关系型数据库 索引
平衡二叉树,红黑树,B树和B+树的区别及其应用场景
平衡二叉树,红黑树,B树和B+树的区别及其应用场景
37 0
|
C++
【C++】AVL树的实现及测试
【C++】AVL树的实现及测试
48 0
学习平衡搜索二叉树(AVL树)【日志】
学习平衡搜索二叉树(AVL树)【日志】
77 0
|
存储 C++
【C++】AVL树模拟实现
当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1,达到高度平衡,即可降低树的高度,从而减少平均搜索长度。即如果一棵二叉搜索树的任意节点左右子树高度差绝对值都<=1,它就是AVL树。 空树也算AVL树
|
存储 数据安全/隐私保护 C++
【C++】哈夫曼树模拟实现
在解释什么是哈夫曼树之前,先介绍三个基本术语:节点的路径长度、节点的权重和树的带权路径长度。