当前位置:   article > 正文

平衡二叉搜索树--AVL详解剖析_avl数

avl数

目录

一、什么是AVL树

二、AVL树的作用

三、树节点的定义

四、节点的插入

五、旋转

1.左单旋

2.右单旋

左右双旋代码 :

4.右左双旋


一、什么是AVL树

AVL树就是二叉搜索树的进一步的优化,二叉搜索树虽可以缩短查找的效率,但是当数据有序或接近有序二叉搜索树变成单支树,在查找的过程中其效率会变得更低下。所以有一种可以达到自平衡二叉搜索树(AVL),它在每次插入或删除节点时会通过平衡因子(高度差)来进行旋转操作保持树的平衡。AVL树的名称来自它的发明者G. M. Adelson-Velsky和E. M. Landis。

AVL树的特点

  1. AVL树的平衡受到平衡因子(高度差)的影响,对于任意节点,其左子树和右子树的高度差不超过1。如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在O(log n),搜索时间复杂度O(log n)
  2. 当向AVL树中插入或删除节点时,就会存在树的平衡性被破坏的可能。所以,为了恢复平衡,AVL树使用四种旋转操作:左旋、右旋、左右旋和右左旋。通过这些旋转操作,AVL树可以在O(log n)时间内完成插入和删除操作,并保持树的平衡。
  3. 因为AVL树保持严格的平衡,因此其在查找、插入和删除操作上的时间复杂度都是O(log n),使得它成为一种高效的数据结构。

二、AVL树的作用

  1.  提供高效的查找操作:AVL树的平衡性保证了在最坏情况下,查找操作的时间复杂度为O(log n),其中n是树中节点的数量。这使得AVL树在需要频繁查找元素的场景下非常有用,例如数据库索引、字典等。
  2. 支持有序遍历:由于AVL树是一种二叉搜索树,其节点按照特定的顺序进行排列。因此,通过对AVL树进行中序遍历,可以按照升序或降序获取树中的所有元素。这使得AVL树在需要按顺序处理数据的场景下很有用,例如范围查询、排序等。
  3. 动态数据集的维护:AVL树的自平衡性适用于动态数据集的维护。当插入或删除节点时,AVL树会通过旋转操作来保持平衡,从而保证树的高度始终保持在O(log n)的范围内。这使得AVL树在需要频繁地插入和删除元素的场景下表现出色,例如实时的数据更新、动态排序等。
  4. 实现其他数据结构:AVL树也可以作为其他数据结构的基础,例如集合、映射、优先队列等。通过在AVL树上实现相应的操作,可以实现这些数据结构,并且保证其在插入、删除和查找等操作上的高效性和平衡性。

三、树节点的定义

  1. template<class K, class V>
  2. struct AVLTreeNode
  3. {
  4. AVLTreeNode<K, V>* _left; //左子节点
  5. AVLTreeNode<K, V>* _right; //右子节点
  6. AVLTreeNode<K, V>* _parent; //父亲节点
  7. pair<K, V> _kv; // 用于存储节点值
  8. int _bf; // 平衡因子
  9. AVLTreeNode(const pair<K, V>& kv)//初始化构造
  10. :_left(nullptr)
  11. , _right(nullptr)
  12. , _parent(nullptr)
  13. , _kv(kv)
  14. , _bf(0)
  15. {}
  16. }
  17. typedef AVLTreeNode<K, V> Node;

四、节点的插入

AVL树的插入主要分为两点:

1.根据二叉搜索树的性质进行插入新的节点

   二叉搜索树性质:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分为二叉搜索树

2.调整节点的平衡因子

调整规则:(parent表示的是插入后的节点的父亲节点)

1、新增在右,父节点的平衡因子parent->bf++;新增在左,父节点的平衡因子parent->bf--;

2、更新后,如果parent->bf== 1 或-1,说明parent插入前的平衡因子是0,左右子树高度相等,插入后有一边高,parent高度变了,所以需要继续更新上面节点的 bf

3、更新后,如果parent->bf ==0,说明parent插入前的平衡因子是1 或 -1,左右子树一边高一边低,插入后两边一样高,插入填上了矮了那边,parent所在子树高度不变,不需要继续往上更新

4、更新后,如果parent-> bf== 2 或 -2,说明parent插入前的平衡因子是1 或 -1,是平衡临界值,插入后变成2 或 -2,打破了平衡,parent所在子树需要根据实际情况进行旋转处理。

5、更新后,如果parent->bf > 2 或 <-2的值,则说明插入前就不是AVL树,需要去检查之前操作的问题。

插入代码讲解:

  1. bool Insert(const pair<K, V>& kv)
  2. {
  3. if (_root == nullptr)//如果是空树,插入的节点就是根节点
  4. {
  5. _root = new Node(kv);
  6. return true;
  7. }
  8. //parent用于记录cur节点的父亲节点,防止在插入新节点时丢失
  9. Node* parent = nullptr;
  10. Node* cur = _root;
  11. // 遍历找到插入新节点的位置,parent记录该位置,cur节点用于探索后面的节点或存储新节点便于插入
  12. while (cur)
  13. {
  14. if (cur->_kv.first < kv.first)
  15. {
  16. parent = cur;
  17. cur = cur->_right;
  18. }
  19. else if (cur->_kv.first > kv.first)
  20. {
  21. parent = cur;
  22. cur = cur->_left;
  23. }
  24. else //一般不可能出现相等的情况,出现了则说明该搜索二叉树构建存在问题
  25. {
  26. return false;
  27. }
  28. }
  29. //cur作为新节点,通过parent与该树进行连接,完成节点插入
  30. cur = new Node(kv);
  31. if (parent->_kv.first < kv.first)
  32. {
  33. parent->_right = cur;
  34. }
  35. else
  36. {
  37. parent->_left = cur;
  38. }
  39. cur->_parent = parent;
  40. // 控制平衡
  41. // 更新平衡因子
  42. //根据上面的调整规则更新平衡因子
  43. while (parent)
  44. {
  45. //规则1
  46. if (cur == parent->_right)
  47. {
  48. parent->_bf++;
  49. }
  50. else
  51. {
  52. parent->_bf--;
  53. }
  54. //规则3
  55. if (parent->_bf == 0)
  56. {
  57. break;
  58. }
  59. //规则2
  60. else if (abs(parent->_bf) == 1)
  61. {
  62. parent = parent->_parent;
  63. cur = cur->_parent;
  64. }
  65. //规则4
  66. else if (abs(parent->_bf) == 2)
  67. {
  68. // 说明parent所在子树已经不平衡了,需要旋转处理
  69. if (parent->_bf == 2 && cur->_bf == 1)
  70. {
  71. RotateL(parent); //左单旋
  72. }
  73. else if ((parent->_bf == -2 && cur->_bf == -1))
  74. {
  75. RotateR(parent); //右单旋
  76. }
  77. else if (parent->_bf == -2 && cur->_bf == 1)
  78. {
  79. RotateLR(parent); // 左右双旋
  80. }
  81. else if (parent->_bf == 2 && cur->_bf == -1)
  82. {
  83. RotateRL(parent); // 右左双旋
  84. }
  85. else
  86. {
  87. assert(false);
  88. }
  89. break;
  90. }
  91. //规则5不存在的可能
  92. else
  93. {
  94. assert(false);
  95. }
  96. }
  97. return true;
  98. }

五、旋转

1.左单旋

旋转原因:

在插入新节点后,父节点parent->bf==2,子节点cur->bf==1。也就是在插入前,该节点bf=1,插入后bf=2,是在该节点的右子树进行插入。如下图:

subR:parent的右子节点

subRL:parent的右子节点的左节点

旋转后subRL的父亲节点,要由原来的subR变为parent节点。subR节点,就变为新的parent节点

 左单旋代码:

  1. void RotateL(Node* parent)
  2. {
  3. Node* subR = parent->_right;
  4. Node* subRL = subR->_left;
  5. //将原来的subRL节点的父亲节点指向为parent
  6. parent->_right = subRL;
  7. if (subRL)
  8. subRL->_parent = parent;
  9. //记录当前parent节点的父节点,保证旋转后,能与主树连接上
  10. Node* ppNode = parent->_parent;
  11. //将subR变为新的parent节点
  12. subR->_left = parent;
  13. parent->_parent = subR;
  14. //将旋转后的子树与原来的主树进行连接
  15. if (_root == parent)
  16. {
  17. _root = subR;
  18. subR->_parent = nullptr;
  19. }
  20. else
  21. {
  22. //判断该子树是原来主树的左子树还是右子树,在进行连接
  23. if (ppNode->_left == parent)
  24. {
  25. ppNode->_left = subR;
  26. }
  27. else
  28. {
  29. ppNode->_right = subR;
  30. }
  31. subR->_parent = ppNode;
  32. }
  33. }

2.右单旋

旋转原因:

parent->bf==-2,cur->bf==-1

在parent的左子树左节点上进行插入

 右旋代码: 

  1. void RotateR(Node* parent)
  2. {
  3. Node* subL = parent->_left;
  4. Node* subLR = subL->_right;
  5. //将subLR与parent相连接
  6. parent->_left = subLR;
  7. if (subLR)
  8. {
  9. subLR->_parent = parent;
  10. }
  11. Node* ppNode = parent->_parent;
  12. //subL变为新的parent节点
  13. subL->_right = parent;
  14. parent->_parent = subL;
  15. //将旋转后的子树与主树进行连接
  16. if (_root == parent)
  17. {
  18. _root = subL;
  19. subL->_parent = nullptr;
  20. }
  21. else
  22. {
  23. if (ppNode->_left == parent)
  24. {
  25. ppNode->_left = subL;
  26. }
  27. else
  28. {
  29. ppNode->_right = subL;
  30. }
  31. subL->_parent = ppNode;
  32. }
  33. subL->_bf = parent->_bf = 0;
  34. }

 3.左右双旋

旋转原因:

parent->bf == -2 && cur->bf == 1,在parent的左子树的右子树上进行插入,破坏了平衡因子。

需要先进行左旋,再进行右旋,再进行调整平衡因子。

左右双旋代码 :

  1. void RotateLR(Node* parent)
  2. {
  3. Node* subL = parent->_left;
  4. Node* subLR = subL->_right;
  5. int bf = subLR->_bf;
  6. //先左旋、再右旋,可以套用上面已实现的方法
  7. RotateL(parent->_left);
  8. RotateR(parent);
  9. subLR->_bf = 0;
  10. //调整平衡因子
  11. //有三种情况
  12. if (bf == 1)
  13. {
  14. parent->_bf = 0;
  15. subL->_bf = -1;
  16. }
  17. else if (bf == -1)
  18. {
  19. parent->_bf = 1;
  20. subL->_bf = 0;
  21. }
  22. else if (bf == 0)
  23. {
  24. parent->_bf = 0;
  25. subL->_bf = 0;
  26. }
  27. else
  28. {
  29. assert(false);
  30. }
  31. }

4.右左双旋

旋转原因:

parent->bf == 2 && cur->bf == -1。插入节点在右子树的左子树上

需要先进行右旋,再进行左旋,再调整平衡因子

 右左旋代码:

  1. void RotateRL(Node* parent)
  2. {
  3. Node* subR = parent->_right;
  4. Node* subRL = subR->_left;
  5. int bf = subRL->_bf;
  6. //先右旋,再左旋
  7. RotateR(parent->_right);
  8. RotateL(parent);
  9. //再调整平衡因子
  10. subRL->_bf = 0;
  11. if (bf == 1)
  12. {
  13. subR->_bf = 0;
  14. parent->_bf = -1;
  15. }
  16. else if (bf == -1)
  17. {
  18. subR->_bf = 1;
  19. parent->_bf = 0;
  20. }
  21. else if (bf == 0)
  22. {
  23. parent->_bf = 0;
  24. subR->_bf = 0;
  25. }
  26. else
  27. {
  28. assert(false);
  29. }
  30. }

 基于以上就是AVL树的概念和旋转的原理和代码的实现。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/511550
推荐阅读
相关标签
  

闽ICP备14008679号