C++ AVL树 详细讲解

大连密封材料  金牌会员 | 2024-6-29 06:56:20 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 455|帖子 455|积分 1365

目次
一、AVL树的概念
二、AVL树的实现
1.AVL树节点的界说
2.AVL树的插入
3.AVL树的旋转
4.AVL树的验证
三、AVL树的性能
四、完结撒❀

一、AVL树的概念

   二叉搜索树虽可以缩短查找的效率,但  如果数据有序或接近有序二叉搜索树将退化为单支树,查     找元素相当于在次序表中搜索元素,效率低下  。因此,两位俄罗斯的数学家  G.M.Adelson-Velskii     和  E.M.Landis  在  1962  年  发明了一种解决上述问题的方法:  当向二叉搜索树中插入新结点后,如果能包管每个结点的左右  子树高度之差的绝对值不超过  1(  需要对树中的结点进行调解  )  ,即可降低树的高度,从而淘汰均匀  搜索长度。     一棵  AVL  树大概是空树,大概是具有以下性子的二叉搜索树:         ● 它的左右子树都是   AVL          ● 左右子树高度之差   (   简称均衡因子   )   的绝对值不超过   1(-1/0/1)   

   如果一棵二叉搜索树是高度均衡的,它就是  AVL  树。如果它有  n  个结点,其高度可保持在    O(log2 n)  ,搜索时间复杂度  O(log2 n)  。    二、AVL树的实现

1.AVL树节点的界说

AVL树节点的界说:
  1. template<class T>
  2. struct AVLTreeNode
  3. {
  4.         AVLTreeNode(const T& data)
  5.                 : _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
  6.                 , _data(data), _bf(0)
  7.         {}
  8.         AVLTreeNode<T>* _pLeft;   // 该节点的左孩子
  9.         AVLTreeNode<T>* _pRight;  // 该节点的右孩子
  10.         AVLTreeNode<T>* _pParent; // 该节点的双亲
  11.         T _data;
  12.         int _bf;                  // 该节点的平衡因子
  13. };
复制代码
2.AVL树的插入

   AVL  树就是在二叉搜索树的基础上引入了均衡因子,因此  AVL  树也可以看成是二叉搜索树。那么     AVL  树的插入过程可以分为两步:         1.    按照二叉搜索树的方式插入新节点        2.    调解节点的均衡因子    
  1. bool Insert(const pair<K, V>& kv)
  2. {
  3.     // 1. 先按照二叉搜索树的规则将节点插入到AVL树中
  4.         if (_root == nullptr)
  5.         {
  6.                 _root = new Node(kv);
  7.                 return true;
  8.         }
  9.         Node* parent = nullptr;
  10.         Node* cur = _root;
  11.         while (cur)
  12.         {
  13.                 if (cur->_kv.first > kv.first)
  14.                 {
  15.                         parent = cur;
  16.                         cur = cur->_left;
  17.                 }
  18.                 else if (cur->_kv.first < kv.first)
  19.                 {
  20.                         parent = cur;
  21.                         cur = cur->_right;
  22.                 }
  23.                 else
  24.                 {
  25.                         //插入相同值
  26.                         return false;
  27.                 }
  28.         }
  29.        
  30.         //找到cur所在位置
  31.         cur = new Node(kv);
  32.         if (parent->_kv.first > cur->_kv.first)
  33.         {
  34.                 parent->_left = cur;
  35.                 cur->_parent = parent;
  36.         }
  37.         else
  38.         {
  39.                 parent->_right = cur;
  40.                 cur->_parent = parent;
  41.         }
  42.      // 2. 新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否
  43.     //破坏了AVL树的平衡性
  44.    
  45.      /*
  46.      pCur插入后,pParent的平衡因子一定需要调整,在插入之前,pParent
  47.      的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:
  48.       1. 如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可
  49.       2. 如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可
  50.  
  51.      此时:pParent的平衡因子可能有三种情况:0,正负1, 正负2
  52.      1. 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整成0,此时满足
  53. AVL树的性质,插入成功
  54.      2. 如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负1,此
  55. 时以pParent为根的树的高度增加,需要继续向上更新
  56.      3. 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理
  57.      */
  58.     //更新平衡因子
  59.         while (parent)
  60.         {
  61.                 if (parent->_left == cur)
  62.                 {
  63.                         parent->_bf--;
  64.                 }
  65.                 else
  66.                 {
  67.                         parent->_bf++;
  68.                 }
  69.                 if (parent->_bf == 0)
  70.                 {
  71.                         //二叉树高度没问题,更新结束
  72.                         break;
  73.                 }
  74.                 else if (parent->_bf == 1 || parent->_bf == -1)
  75.                 {
  76.            // 插入前双亲的平衡因子是0,插入后双亲的平衡因子为1 或者 -1 ,说明以双亲为根的二叉树
  77.            // 的高度增加了一层,因此需要继续向上调整
  78.                         cur = parent;
  79.                         parent = parent->_parent;
  80.                 }
  81.                 else if (parent->_bf == -2 || parent->_bf == 2)
  82.                 {
  83.             //双亲的平衡因子为正负2,违反了AVL树的平衡性
  84.                         //二叉树平衡被破坏,需要旋转完成平衡
  85.                         //判断是右单旋还是左单旋还是双旋
  86.                        
  87.                         //右单旋
  88.                         if (parent->_bf == -2 && cur->_bf == -1)
  89.                         {
  90.                                 //...
  91.                         }
  92.                         //左单旋
  93.                         else if (parent->_bf == 2 && cur->_bf == 1)
  94.                         {
  95.                                 //...
  96.                         }
  97.                         //左右双旋
  98.                         else if (parent->_bf == -2 && cur->_bf == 1)
  99.                         {
  100.                 //...
  101.                         }
  102.                         //右左双旋
  103.                         else if (parent->_bf == 2 && cur->_bf == -1)
  104.                         {
  105.                 //...
  106.                         }
  107.                 }
  108.                 else
  109.                 {
  110.                         //理论上不会出现这种状况
  111.                         assert(false);
  112.                 }
  113.         }
  114.         return true;
  115. }
复制代码
3.AVL树的旋转

   如果在一棵本来是均衡的  AVL  树中插入一个新节点,可能造成不均衡,此时必须调解树的结构,     使之均衡化。根据节点插入位置的差别,  AVL  树的旋转分为四种:      1) 新节点插入较高左子树的左侧---左左:右单旋   

  1. /*
  2.  上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左
  3. 子树增加
  4.  了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子
  5. 树增加一层,
  6.  即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有
  7. 右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点
  8. 的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:
  9.  1. 30节点的右孩子可能存在,也可能不存在
  10.  2. 60可能是根节点,也可能是子树
  11.     如果是根节点,旋转完成后,要更新根节点
  12.     如果是子树,可能是某个节点的左子树,也可能是右子树
  13.    
  14. 此处可举一些详细的例子进行画图,考虑各种情况,加深旋转的理解
  15. */
  16. void _RotateR(Node Parent)
  17. {
  18.         // SubL: Parent的左孩子
  19.         // SubLR: Parent左孩子的右孩子,注意:该
  20.         Node SubL = Parent->_Left;
  21.         Node SubLR = SubL->_Right;
  22.         // 旋转完成之后,30的右孩子作为双亲的左孩子
  23.         Parent->_Left = SubLR;
  24.         // 如果30的左孩子的右孩子存在,更新亲双亲
  25.         if (SubLR)
  26.                 SubLR->_Parent = Parent;
  27.         // 60 作为 30的右孩子
  28.         SubL->_Right = Parent;
  29.         // 因为60可能是棵子树,因此在更新其双亲前必须先保存60的双亲
  30.         Node Parent = Parent->_Parent;
  31.         // 更新60的双亲
  32.         Parent->_Parent = SubL;
  33.         // 更新30的双亲
  34.         SubL->_Parent = Parent;
  35.         // 如果60是根节点,根新指向根节点的指针
  36.         if (NULL == Parent)
  37.         {
  38.                 _root = SubL;
  39.                 SubL->_Parent = NULL;
  40.         }
  41.         else
  42.         {
  43.                 // 如果60是子树,可能是其双亲的左子树,也可能是右子树
  44.                 if (Parent->_Left == Parent)
  45.                         Parent->_Left = SubL;
  46.                 else
  47.                         Parent->_Right = SubL;
  48.         }
  49.         // 根据调整后的结构更新部分节点的平衡因子
  50.         Parent->_bf = SubL->_bf = 0;
  51. }
复制代码
2) 新节点插入较高右子树的右侧---右右:左单旋

  
  1. //左单旋
  2. void LNode(Node* parent)
  3. {
  4.         /*if (parent == _root)
  5.         {
  6.                 Node* pparent = nullptr;
  7.         }
  8.         else
  9.         {
  10.                 Node* pparent = parent->_parent;
  11.         }*/
  12.         Node* pparent = parent->_parent;
  13.         Node* subR = parent->_right;
  14.         Node* subRL = subR->_left;
  15.         parent->_left = subRL;
  16.         if (subRL)
  17.                 subRL->_parent = parent;
  18.         subR->_left = parent;
  19.         parent->_parent = subR;
  20.         if (pparent)
  21.         {
  22.                 subR->_parent = pparent;
  23.                 if (pparent->_left = parent)
  24.                 {
  25.                         pparent->_left = subR;
  26.                 }
  27.                 else
  28.                 {
  29.                         pparent->_right = subR;
  30.                 }
  31.         }
  32.         else
  33.         {
  34.                 _root = subR;
  35.                 subR->_parent = nullptr;
  36.         }
  37.         parent->_bf = subR->_bf = 0;
  38. }
复制代码
3) 新节点插入较高左子树的右侧---左右:先左单旋再右单旋
  

   将双旋变成单旋后再旋转,即:  先对  30  进行左单旋,然后再对  90  进行右单旋  ,旋转完成后再     考虑均衡因子的更新。  
  1. // 旋转之前,60的平衡因子可能是-1/0/1,旋转完成之后,根据情况对其他节点的平衡因子进
  2. //行调整
  3. void _RotateLR(Node Parent)
  4. {
  5.         Node SubL = Parent->_Left;
  6.         Node SubLR = SubL->_Right;
  7.         // 旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节
  8.         点的平衡因子
  9.                 int bf = SubLR->_bf;
  10.         // 先对30进行左单旋
  11.         _RotateL(Parent->_Left);
  12.         // 再对90进行右单旋
  13.         _RotateR(Parent);
  14.         if (1 == bf)
  15.                 SubL->_bf = -1;
  16.         else if (-1 == bf)
  17.                 Parent->_bf = 1;
  18. }
复制代码
4) 新节点插入较高右子树的左侧---右左:先右单旋再左单旋

  1. //右左双旋
  2. void RLNode(Node* parent)
  3. {
  4.         Node* subR = parent->_right;
  5.         Node* subRL = subR->_left;
  6.         int bf = subRL->_bf;
  7.         RNode(parent->_right);
  8.         LNode(parent);
  9.         if (bf == 1)
  10.         {
  11.                 subRL->_bf = 0;
  12.                 subR->_bf = 0;
  13.                 parent->_bf = -1;
  14.         }
  15.         else if (bf == -1)
  16.         {
  17.                 subRL->_bf = 0;
  18.                 subR->_bf = 1;
  19.                 parent->_bf = 0;
  20.         }
  21.         else if (bf == 0)
  22.         {
  23.                 subRL->_bf = 0;
  24.                 subR->_bf = 0;
  25.                 parent->_bf = 0;
  26.         }
  27.         else
  28.         {
  29.                 //理论没有该状况
  30.                 assert(false);
  31.         }
  32. }
复制代码
  总结:      如果以  Parent  为根的子树不均衡,即  Parent  的均衡因子为  2  大概  -2  ,分以下环境考虑      1)Parent  的均衡因子为  2  ,说明  Parent  的右子树高,设  Parent  的右子树的根为  SubR        当   SubR   的均衡因子为   1   时,执行左单旋        当   SubR   的均衡因子为   -1   时,执行右左双旋       2)Parent  的均衡因子为  -2  ,说明  Parent  的左子树高,设  Parent  的左子树的根为  SubL        当   SubL   的均衡因子为   -1   是,执行右单旋        当   SubL   的均衡因子为   1   时,执行左右双旋       旋转完成后,原  Parent  为根的子树个高度降低,已经均衡,不需要再向上更新。  4.AVL树的验证

   AVL  树是在二叉搜索树的基础上参加了均衡性的限制,因此要验证  AVL  树,可以分两步:              1. 验证其为二叉搜索树                 如果中序遍历可得到一个有序的序列,就说明为二叉搜索树              2. 验证其为均衡树                 ● 每个节点子树高度差的绝对值不超过1(注意节点中如果没有均衡因子  )                  ● 节点的均衡因子是否盘算准确
  1. int _size(Node* root)
  2. {
  3.         return root == nullptr ? 0 : _size(root->_left) + _size(root->_right) + 1;
  4. }
  5. int _Height(Node* root)
  6. {
  7.         if (root == nullptr)
  8.         {
  9.                 return 0;
  10.         }
  11.         return max(_Height(root->_left), _Height(root->_right)) + 1;
  12. }
  13. bool _IsBalance(Node* root)
  14. {
  15.         if (root == nullptr)
  16.         {
  17.                 return true;
  18.         }
  19.         int LeftHeight = _Height(root->_left);
  20.         int RightHeight = _Height(root->_right);
  21.         if (abs(LeftHeight - RightHeight) >= 2)
  22.         {
  23.                 return false;
  24.         }
  25.         //可以顺便再检查一下平衡因子
  26.         if (abs(LeftHeight - RightHeight) != root->_bf)
  27.         {
  28.                 cout << root->_kv.first << endl;
  29.                 return false;
  30.         }
  31.         return _IsBalance(root->_left) && _IsBalance(root->_right);
  32. }
复制代码
三、AVL树的性能

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

如果以上内容对你有资助不妨点赞支持一下,以后还会分享更多编程知识,我们一起进步。
最后我想讲的是,听说点赞的都能找到美丽女朋友❤


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

大连密封材料

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表